diff options
Diffstat (limited to 'tests/wpt/web-platform-tests')
2386 files changed, 83732 insertions, 34145 deletions
diff --git a/tests/wpt/web-platform-tests/.azure-pipelines.yml b/tests/wpt/web-platform-tests/.azure-pipelines.yml index 9f974615e35..29b19e5726c 100644 --- a/tests/wpt/web-platform-tests/.azure-pipelines.yml +++ b/tests/wpt/web-platform-tests/.azure-pipelines.yml @@ -497,8 +497,7 @@ jobs: channel: stable - template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_manifest.yml - # --exclude is a workaround for https://github.com/web-platform-tests/wpt/issues/18995 + https://github.com/web-platform-tests/wpt/issues/20887 + https://github.com/web-platform-tests/wpt/issues/22175 + https://github.com/web-platform-tests/wpt/issues/23630 + https://github.com/web-platform-tests/wpt/issues/25294 - - script: ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel stable safari --exclude /pointerevents/pointerevent_pointercapture-not-lost-in-chorded-buttons.html --exclude /pointerevents/pointerevent_pointercapture_in_frame.html --exclude /web-share/share-sharePromise-internal-slot.https.html --exclude /webdriver/tests/perform_actions/pointer_tripleclick.py --exclude /is-input-pending/security/cross-origin-subframe-overlap.sub.html --exclude /web-share/share-url-invalid.https.html + - script: ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel stable --kill-safari safari displayName: 'Run tests' - task: PublishBuildArtifacts@1 displayName: 'Publish results' @@ -533,8 +532,7 @@ jobs: - template: tools/ci/azure/install_safari.yml - template: tools/ci/azure/update_hosts.yml - template: tools/ci/azure/update_manifest.yml - # --exclude is a workaround for https://github.com/web-platform-tests/wpt/issues/18995 + https://github.com/web-platform-tests/wpt/issues/20887 + https://github.com/web-platform-tests/wpt/issues/23630 + https://github.com/web-platform-tests/wpt/issues/25417 - - script: ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel preview safari --exclude /pointerevents/pointerevent_pointercapture_in_frame.html --exclude /web-share/share-sharePromise-internal-slot.https.html --exclude /webdriver/tests/perform_actions/pointer_tripleclick.py --exclude /web-share/share-url-invalid.https.html + - script: ./wpt run --no-manifest-update --no-restart-on-unexpected --no-fail-on-unexpected --this-chunk=$(System.JobPositionInPhase) --total-chunks=$(System.TotalJobsInPhase) --chunk-type hash --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report_$(System.JobPositionInPhase).json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot_$(System.JobPositionInPhase).txt --log-mach - --log-mach-level info --channel preview --kill-safari safari displayName: 'Run tests' - task: PublishBuildArtifacts@1 displayName: 'Publish results' diff --git a/tests/wpt/web-platform-tests/.github/workflows/regen_certs.yml b/tests/wpt/web-platform-tests/.github/workflows/regen_certs.yml index a2f5834bfc9..33699d833aa 100644 --- a/tests/wpt/web-platform-tests/.github/workflows/regen_certs.yml +++ b/tests/wpt/web-platform-tests/.github/workflows/regen_certs.yml @@ -8,6 +8,10 @@ jobs: update: runs-on: ubuntu-latest steps: + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' - name: Checkout uses: actions/checkout@v2 - name: Regenerate certs diff --git a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor-dom.window.js b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor-dom.window.js new file mode 100644 index 00000000000..4fd4a43ec4b --- /dev/null +++ b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor-dom.window.js @@ -0,0 +1,53 @@ +// META: title=Blob constructor +// META: script=../support/Blob.js +'use strict'; + +var test_error = { + name: "test", + message: "test error", +}; + +test(function() { + var args = [ + document.createElement("div"), + window, + ]; + args.forEach(function(arg) { + assert_throws_js(TypeError, function() { + new Blob(arg); + }, "Should throw for argument " + format_value(arg) + "."); + }); +}, "Passing platform objects for blobParts should throw a TypeError."); + +test(function() { + var element = document.createElement("div"); + element.appendChild(document.createElement("div")); + element.appendChild(document.createElement("p")); + var list = element.children; + Object.defineProperty(list, "length", { + get: function() { throw test_error; } + }); + assert_throws_exactly(test_error, function() { + new Blob(list); + }); +}, "A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)"); + +test_blob(function() { + var select = document.createElement("select"); + select.appendChild(document.createElement("option")); + return new Blob(select); +}, { + expected: "[object HTMLOptionElement]", + type: "", + desc: "Passing an platform object that supports indexed properties as the blobParts array should work (select)." +}); + +test_blob(function() { + var elm = document.createElement("div"); + elm.setAttribute("foo", "bar"); + return new Blob(elm.attributes); +}, { + expected: "[object Attr]", + type: "", + desc: "Passing an platform object that supports indexed properties as the blobParts array should work (attributes)." +});
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor.any.js b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor.any.js new file mode 100644 index 00000000000..6c34d7e34b9 --- /dev/null +++ b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor.any.js @@ -0,0 +1,459 @@ +// META: title=Blob constructor +// META: script=../support/Blob.js +'use strict'; + +test(function() { + assert_true("Blob" in globalThis, "globalThis should have a Blob property."); + assert_equals(Blob.length, 0, "Blob.length should be 0."); + assert_true(Blob instanceof Function, "Blob should be a function."); +}, "Blob interface object"); + +// Step 1. +test(function() { + var blob = new Blob(); + assert_true(blob instanceof Blob); + assert_equals(String(blob), '[object Blob]'); + assert_equals(blob.size, 0); + assert_equals(blob.type, ""); +}, "Blob constructor with no arguments"); +test(function() { + assert_throws_js(TypeError, function() { var blob = Blob(); }); +}, "Blob constructor with no arguments, without 'new'"); +test(function() { + var blob = new Blob; + assert_true(blob instanceof Blob); + assert_equals(blob.size, 0); + assert_equals(blob.type, ""); +}, "Blob constructor without brackets"); +test(function() { + var blob = new Blob(undefined); + assert_true(blob instanceof Blob); + assert_equals(String(blob), '[object Blob]'); + assert_equals(blob.size, 0); + assert_equals(blob.type, ""); +}, "Blob constructor with undefined as first argument"); + +// blobParts argument (WebIDL). +test(function() { + var args = [ + null, + true, + false, + 0, + 1, + 1.5, + "FAIL", + new Date(), + new RegExp(), + {}, + { 0: "FAIL", length: 1 }, + ]; + args.forEach(function(arg) { + assert_throws_js(TypeError, function() { + new Blob(arg); + }, "Should throw for argument " + format_value(arg) + "."); + }); +}, "Passing non-objects, Dates and RegExps for blobParts should throw a TypeError."); + +test_blob(function() { + return new Blob({ + [Symbol.iterator]: Array.prototype[Symbol.iterator], + }); +}, { + expected: "", + type: "", + desc: "A plain object with @@iterator should be treated as a sequence for the blobParts argument." +}); +test(t => { + const blob = new Blob({ + [Symbol.iterator]() { + var i = 0; + return {next: () => [ + {done:false, value:'ab'}, + {done:false, value:'cde'}, + {done:true} + ][i++] + }; + } + }); + assert_equals(blob.size, 5, 'Custom @@iterator should be treated as a sequence'); +}, "A plain object with custom @@iterator should be treated as a sequence for the blobParts argument."); +test_blob(function() { + return new Blob({ + [Symbol.iterator]: Array.prototype[Symbol.iterator], + 0: "PASS", + length: 1 + }); +}, { + expected: "PASS", + type: "", + desc: "A plain object with @@iterator and a length property should be treated as a sequence for the blobParts argument." +}); +test_blob(function() { + return new Blob(new String("xyz")); +}, { + expected: "xyz", + type: "", + desc: "A String object should be treated as a sequence for the blobParts argument." +}); +test_blob(function() { + return new Blob(new Uint8Array([1, 2, 3])); +}, { + expected: "123", + type: "", + desc: "A Uint8Array object should be treated as a sequence for the blobParts argument." +}); + +var test_error = { + name: "test", + message: "test error", +}; + +test(function() { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + get length() { throw test_error; } + }; + assert_throws_exactly(test_error, function() { + new Blob(obj); + }); +}, "The length getter should be invoked and any exceptions should be propagated."); + +test(function() { + assert_throws_exactly(test_error, function() { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + length: { + valueOf: null, + toString: function() { throw test_error; } + } + }; + new Blob(obj); + }); + assert_throws_exactly(test_error, function() { + var obj = { + [Symbol.iterator]: Array.prototype[Symbol.iterator], + length: { valueOf: function() { throw test_error; } } + }; + new Blob(obj); + }); +}, "ToUint32 should be applied to the length and any exceptions should be propagated."); + +test(function() { + var received = []; + var obj = { + get [Symbol.iterator]() { + received.push("Symbol.iterator"); + return Array.prototype[Symbol.iterator]; + }, + get length() { + received.push("length getter"); + return { + valueOf: function() { + received.push("length valueOf"); + return 3; + } + }; + }, + get 0() { + received.push("0 getter"); + return { + toString: function() { + received.push("0 toString"); + return "a"; + } + }; + }, + get 1() { + received.push("1 getter"); + throw test_error; + }, + get 2() { + received.push("2 getter"); + assert_unreached("Should not call the getter for 2 if the getter for 1 threw."); + } + }; + assert_throws_exactly(test_error, function() { + new Blob(obj); + }); + assert_array_equals(received, [ + "Symbol.iterator", + "length getter", + "length valueOf", + "0 getter", + "0 toString", + "length getter", + "length valueOf", + "1 getter", + ]); +}, "Getters and value conversions should happen in order until an exception is thrown."); + +// XXX should add tests edge cases of ToLength(length) + +test(function() { + assert_throws_exactly(test_error, function() { + new Blob([{ toString: function() { throw test_error; } }]); + }, "Throwing toString"); + assert_throws_exactly(test_error, function() { + new Blob([{ toString: undefined, valueOf: function() { throw test_error; } }]); + }, "Throwing valueOf"); + assert_throws_exactly(test_error, function() { + new Blob([{ + toString: function() { throw test_error; }, + valueOf: function() { assert_unreached("Should not call valueOf if toString is present."); } + }]); + }, "Throwing toString and valueOf"); + assert_throws_js(TypeError, function() { + new Blob([{toString: null, valueOf: null}]); + }, "Null toString and valueOf"); +}, "ToString should be called on elements of the blobParts array and any exceptions should be propagated."); + +test_blob(function() { + var arr = [ + { toString: function() { arr.pop(); return "PASS"; } }, + { toString: function() { assert_unreached("Should have removed the second element of the array rather than called toString() on it."); } } + ]; + return new Blob(arr); +}, { + expected: "PASS", + type: "", + desc: "Changes to the blobParts array should be reflected in the returned Blob (pop)." +}); + +test_blob(function() { + var arr = [ + { + toString: function() { + if (arr.length === 3) { + return "A"; + } + arr.unshift({ + toString: function() { + assert_unreached("Should only access index 0 once."); + } + }); + return "P"; + } + }, + { + toString: function() { + return "SS"; + } + } + ]; + return new Blob(arr); +}, { + expected: "PASS", + type: "", + desc: "Changes to the blobParts array should be reflected in the returned Blob (unshift)." +}); + +test_blob(function() { + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652 + return new Blob([ + null, + undefined, + true, + false, + 0, + 1, + new String("stringobject"), + [], + ['x', 'y'], + {}, + { 0: "FAIL", length: 1 }, + { toString: function() { return "stringA"; } }, + { toString: undefined, valueOf: function() { return "stringB"; } }, + { valueOf: function() { assert_unreached("Should not call valueOf if toString is present on the prototype."); } } + ]); +}, { + expected: "nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]", + type: "", + desc: "ToString should be called on elements of the blobParts array." +}); + +test_blob(function() { + return new Blob([ + new ArrayBuffer(8) + ]); +}, { + expected: "\0\0\0\0\0\0\0\0", + type: "", + desc: "ArrayBuffer elements of the blobParts array should be supported." +}); + +test_blob(function() { + return new Blob([ + new Uint8Array([0x50, 0x41, 0x53, 0x53]), + new Int8Array([0x50, 0x41, 0x53, 0x53]), + new Uint16Array([0x4150, 0x5353]), + new Int16Array([0x4150, 0x5353]), + new Uint32Array([0x53534150]), + new Int32Array([0x53534150]), + new Float32Array([0xD341500000]) + ]); +}, { + expected: "PASSPASSPASSPASSPASSPASSPASS", + type: "", + desc: "Passing typed arrays as elements of the blobParts array should work." +}); +test_blob(function() { + return new Blob([ + // 0x535 3415053534150 + // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310 + // 0x13415053534150 * 2**(-52) + // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680 + new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680]) + ]); +}, { + expected: "PASSPASS", + type: "", + desc: "Passing a Float64Array as element of the blobParts array should work." +}); + + + +var t_ports = async_test("Passing a FrozenArray as the blobParts array should work (FrozenArray<MessagePort>)."); +t_ports.step(function() { + var channel = new MessageChannel(); + channel.port2.onmessage = this.step_func(function(e) { + var b_ports = new Blob(e.ports); + assert_equals(b_ports.size, "[object MessagePort]".length); + this.done(); + }); + var channel2 = new MessageChannel(); + channel.port1.postMessage('', [channel2.port1]); +}); + +test_blob(function() { + var blob = new Blob(['foo']); + return new Blob([blob, blob]); +}, { + expected: "foofoo", + type: "", + desc: "Array with two blobs" +}); + +test_blob_binary(function() { + var view = new Uint8Array([0, 255, 0]); + return new Blob([view.buffer, view.buffer]); +}, { + expected: [0, 255, 0, 0, 255, 0], + type: "", + desc: "Array with two buffers" +}); + +test_blob_binary(function() { + var view = new Uint8Array([0, 255, 0, 4]); + var blob = new Blob([view, view]); + assert_equals(blob.size, 8); + var view1 = new Uint16Array(view.buffer, 2); + return new Blob([view1, view.buffer, view1]); +}, { + expected: [0, 4, 0, 255, 0, 4, 0, 4], + type: "", + desc: "Array with two bufferviews" +}); + +test_blob(function() { + var view = new Uint8Array([0]); + var blob = new Blob(["fo"]); + return new Blob([view.buffer, blob, "foo"]); +}, { + expected: "\0fofoo", + type: "", + desc: "Array with mixed types" +}); + +test(function() { + const accessed = []; + const stringified = []; + + new Blob([], { + get type() { accessed.push('type'); }, + get endings() { accessed.push('endings'); } + }); + new Blob([], { + type: { toString: () => { stringified.push('type'); return ''; } }, + endings: { toString: () => { stringified.push('endings'); return 'transparent'; } } + }); + assert_array_equals(accessed, ['endings', 'type']); + assert_array_equals(stringified, ['endings', 'type']); +}, "options properties should be accessed in lexicographic order."); + +test(function() { + assert_throws_exactly(test_error, function() { + new Blob( + [{ toString: function() { throw test_error } }], + { + get type() { assert_unreached("type getter should not be called."); } + } + ); + }); +}, "Arguments should be evaluated from left to right."); + +[ + null, + undefined, + {}, + { unrecognized: true }, + /regex/, + function() {} +].forEach(function(arg, idx) { + test_blob(function() { + return new Blob([], arg); + }, { + expected: "", + type: "", + desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults." + }); + test_blob(function() { + return new Blob(["\na\r\nb\n\rc\r"], arg); + }, { + expected: "\na\r\nb\n\rc\r", + type: "", + desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults (with newlines)." + }); +}); + +[ + 123, + 123.4, + true, + 'abc' +].forEach(arg => { + test(t => { + assert_throws_js(TypeError, () => new Blob([], arg), + 'Blob constructor should throw with invalid property bag'); + }, `Passing ${JSON.stringify(arg)} for options should throw`); +}); + +var type_tests = [ + // blobParts, type, expected type + [[], '', ''], + [[], 'a', 'a'], + [[], 'A', 'a'], + [[], 'text/html', 'text/html'], + [[], 'TEXT/HTML', 'text/html'], + [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'], + [[], '\u00E5', ''], + [[], '\uD801\uDC7E', ''], // U+1047E + [[], ' image/gif ', ' image/gif '], + [[], '\timage/gif\t', ''], + [[], 'image/gif;\u007f', ''], + [[], '\u0130mage/gif', ''], // uppercase i with dot + [[], '\u0131mage/gif', ''], // lowercase dotless i + [[], 'image/gif\u0000', ''], + // check that type isn't changed based on sniffing + [[0x3C, 0x48, 0x54, 0x4D, 0x4C, 0x3E], 'unknown/unknown', 'unknown/unknown'], // "<HTML>" + [[0x00, 0xFF], 'text/plain', 'text/plain'], + [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a" +]; + +type_tests.forEach(function(t) { + test(function() { + var arr = new Uint8Array([t[0]]).buffer; + var b = new Blob([arr], {type:t[1]}); + assert_equals(b.type, t[2]); + }, "Blob with type " + format_value(t[1])); +}); diff --git a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor.html b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor.html deleted file mode 100644 index 62a649aed66..00000000000 --- a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-constructor.html +++ /dev/null @@ -1,501 +0,0 @@ -<!DOCTYPE html> -<meta charset=utf-8> -<title>Blob constructor</title> -<link rel=help href="http://dev.w3.org/2006/webapi/FileAPI/#constructorBlob"> -<link rel=help href="https://heycam.github.io/webidl/#es-union"> -<link rel=help href="https://heycam.github.io/webidl/#es-dictionary"> -<link rel=help href="https://heycam.github.io/webidl/#es-sequence"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="../support/Blob.js"></script> -<div id="log"></div> -<script> -test(function() { - assert_true("Blob" in window, "window should have a Blob property."); - assert_equals(Blob.length, 0, "Blob.length should be 0."); - assert_true(Blob instanceof Function, "Blob should be a function."); -}, "Blob interface object"); - -// Step 1. -test(function() { - var blob = new Blob(); - assert_true(blob instanceof Blob); - assert_equals(String(blob), '[object Blob]'); - assert_equals(blob.size, 0); - assert_equals(blob.type, ""); -}, "Blob constructor with no arguments"); -test(function() { - assert_throws_js(TypeError, function() { var blob = Blob(); }); -}, "Blob constructor with no arguments, without 'new'"); -test(function() { - var blob = new Blob; - assert_true(blob instanceof Blob); - assert_equals(blob.size, 0); - assert_equals(blob.type, ""); -}, "Blob constructor without brackets"); -test(function() { - var blob = new Blob(undefined); - assert_true(blob instanceof Blob); - assert_equals(String(blob), '[object Blob]'); - assert_equals(blob.size, 0); - assert_equals(blob.type, ""); -}, "Blob constructor with undefined as first argument"); - -// blobParts argument (WebIDL). -test(function() { - var args = [ - null, - true, - false, - 0, - 1, - 1.5, - "FAIL", - new Date(), - new RegExp(), - {}, - { 0: "FAIL", length: 1 }, - document.createElement("div"), - window, - ]; - args.forEach(function(arg) { - assert_throws_js(TypeError, function() { - new Blob(arg); - }, "Should throw for argument " + format_value(arg) + "."); - }); -}, "Passing non-objects, Dates and RegExps for blobParts should throw a TypeError."); - -test_blob(function() { - return new Blob({ - [Symbol.iterator]: Array.prototype[Symbol.iterator], - }); -}, { - expected: "", - type: "", - desc: "A plain object with @@iterator should be treated as a sequence for the blobParts argument." -}); -test(t => { - const blob = new Blob({ - [Symbol.iterator]() { - var i = 0; - return {next: () => [ - {done:false, value:'ab'}, - {done:false, value:'cde'}, - {done:true} - ][i++] - }; - } - }); - assert_equals(blob.size, 5, 'Custom @@iterator should be treated as a sequence'); -}, "A plain object with custom @@iterator should be treated as a sequence for the blobParts argument."); -test_blob(function() { - return new Blob({ - [Symbol.iterator]: Array.prototype[Symbol.iterator], - 0: "PASS", - length: 1 - }); -}, { - expected: "PASS", - type: "", - desc: "A plain object with @@iterator and a length property should be treated as a sequence for the blobParts argument." -}); -test_blob(function() { - return new Blob(new String("xyz")); -}, { - expected: "xyz", - type: "", - desc: "A String object should be treated as a sequence for the blobParts argument." -}); -test_blob(function() { - return new Blob(new Uint8Array([1, 2, 3])); -}, { - expected: "123", - type: "", - desc: "A Uint8Array object should be treated as a sequence for the blobParts argument." -}); - -var test_error = { - name: "test", - message: "test error", -}; - -test(function() { - var obj = { - [Symbol.iterator]: Array.prototype[Symbol.iterator], - get length() { throw test_error; } - }; - assert_throws_exactly(test_error, function() { - new Blob(obj); - }); -}, "The length getter should be invoked and any exceptions should be propagated."); - -test(function() { - var element = document.createElement("div"); - element.appendChild(document.createElement("div")); - element.appendChild(document.createElement("p")); - var list = element.children; - Object.defineProperty(list, "length", { - get: function() { throw test_error; } - }); - assert_throws_exactly(test_error, function() { - new Blob(list); - }); -}, "A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)"); - -test(function() { - assert_throws_exactly(test_error, function() { - var obj = { - [Symbol.iterator]: Array.prototype[Symbol.iterator], - length: { - valueOf: null, - toString: function() { throw test_error; } - } - }; - new Blob(obj); - }); - assert_throws_exactly(test_error, function() { - var obj = { - [Symbol.iterator]: Array.prototype[Symbol.iterator], - length: { valueOf: function() { throw test_error; } } - }; - new Blob(obj); - }); -}, "ToUint32 should be applied to the length and any exceptions should be propagated."); - -test(function() { - var received = []; - var obj = { - get [Symbol.iterator]() { - received.push("Symbol.iterator"); - return Array.prototype[Symbol.iterator]; - }, - get length() { - received.push("length getter"); - return { - valueOf: function() { - received.push("length valueOf"); - return 3; - } - }; - }, - get 0() { - received.push("0 getter"); - return { - toString: function() { - received.push("0 toString"); - return "a"; - } - }; - }, - get 1() { - received.push("1 getter"); - throw test_error; - }, - get 2() { - received.push("2 getter"); - assert_unreached("Should not call the getter for 2 if the getter for 1 threw."); - } - }; - assert_throws_exactly(test_error, function() { - new Blob(obj); - }); - assert_array_equals(received, [ - "Symbol.iterator", - "length getter", - "length valueOf", - "0 getter", - "0 toString", - "length getter", - "length valueOf", - "1 getter", - ]); -}, "Getters and value conversions should happen in order until an exception is thrown."); - -// XXX should add tests edge cases of ToLength(length) - -test(function() { - assert_throws_exactly(test_error, function() { - new Blob([{ toString: function() { throw test_error; } }]); - }, "Throwing toString"); - assert_throws_exactly(test_error, function() { - new Blob([{ toString: undefined, valueOf: function() { throw test_error; } }]); - }, "Throwing valueOf"); - assert_throws_exactly(test_error, function() { - new Blob([{ - toString: function() { throw test_error; }, - valueOf: function() { assert_unreached("Should not call valueOf if toString is present."); } - }]); - }, "Throwing toString and valueOf"); - assert_throws_js(TypeError, function() { - new Blob([{toString: null, valueOf: null}]); - }, "Null toString and valueOf"); -}, "ToString should be called on elements of the blobParts array and any exceptions should be propagated."); - -test_blob(function() { - var arr = [ - { toString: function() { arr.pop(); return "PASS"; } }, - { toString: function() { assert_unreached("Should have removed the second element of the array rather than called toString() on it."); } } - ]; - return new Blob(arr); -}, { - expected: "PASS", - type: "", - desc: "Changes to the blobParts array should be reflected in the returned Blob (pop)." -}); - -test_blob(function() { - var arr = [ - { - toString: function() { - if (arr.length === 3) { - return "A"; - } - arr.unshift({ - toString: function() { - assert_unreached("Should only access index 0 once."); - } - }); - return "P"; - } - }, - { - toString: function() { - return "SS"; - } - } - ]; - return new Blob(arr); -}, { - expected: "PASS", - type: "", - desc: "Changes to the blobParts array should be reflected in the returned Blob (unshift)." -}); - -test_blob(function() { - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=17652 - return new Blob([ - null, - undefined, - true, - false, - 0, - 1, - new String("stringobject"), - [], - ['x', 'y'], - {}, - { 0: "FAIL", length: 1 }, - { toString: function() { return "stringA"; } }, - { toString: undefined, valueOf: function() { return "stringB"; } }, - { valueOf: function() { assert_unreached("Should not call valueOf if toString is present on the prototype."); } } - ]); -}, { - expected: "nullundefinedtruefalse01stringobjectx,y[object Object][object Object]stringAstringB[object Object]", - type: "", - desc: "ToString should be called on elements of the blobParts array." -}); - -test_blob(function() { - return new Blob([ - new ArrayBuffer(8) - ]); -}, { - expected: "\0\0\0\0\0\0\0\0", - type: "", - desc: "ArrayBuffer elements of the blobParts array should be supported." -}); - -test_blob(function() { - return new Blob([ - new Uint8Array([0x50, 0x41, 0x53, 0x53]), - new Int8Array([0x50, 0x41, 0x53, 0x53]), - new Uint16Array([0x4150, 0x5353]), - new Int16Array([0x4150, 0x5353]), - new Uint32Array([0x53534150]), - new Int32Array([0x53534150]), - new Float32Array([0xD341500000]) - ]); -}, { - expected: "PASSPASSPASSPASSPASSPASSPASS", - type: "", - desc: "Passing typed arrays as elements of the blobParts array should work." -}); -test_blob(function() { - return new Blob([ - // 0x535 3415053534150 - // 0x535 = 0b010100110101 -> Sign = +, Exponent = 1333 - 1023 = 310 - // 0x13415053534150 * 2**(-52) - // ==> 0x13415053534150 * 2**258 = 2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680 - new Float64Array([2510297372767036725005267563121821874921913208671273727396467555337665343087229079989707079680]) - ]); -}, { - expected: "PASSPASS", - type: "", - desc: "Passing a Float64Array as element of the blobParts array should work." -}); - -test_blob(function() { - var select = document.createElement("select"); - select.appendChild(document.createElement("option")); - return new Blob(select); -}, { - expected: "[object HTMLOptionElement]", - type: "", - desc: "Passing an platform object that supports indexed properties as the blobParts array should work (select)." -}); - -test_blob(function() { - var elm = document.createElement("div"); - elm.setAttribute("foo", "bar"); - return new Blob(elm.attributes); -}, { - expected: "[object Attr]", - type: "", - desc: "Passing an platform object that supports indexed properties as the blobParts array should work (attributes)." -}); - -var t_ports = async_test("Passing a FrozenArray as the blobParts array should work (FrozenArray<MessagePort>)."); -t_ports.step(function() { - var channel = new MessageChannel(); - channel.port2.onmessage = this.step_func(function(e) { - var b_ports = new Blob(e.ports); - assert_equals(b_ports.size, "[object MessagePort]".length); - this.done(); - }); - var channel2 = new MessageChannel(); - channel.port1.postMessage('', [channel2.port1]); -}); - -test_blob(function() { - var blob = new Blob(['foo']); - return new Blob([blob, blob]); -}, { - expected: "foofoo", - type: "", - desc: "Array with two blobs" -}); - -test_blob_binary(function() { - var view = new Uint8Array([0, 255, 0]); - return new Blob([view.buffer, view.buffer]); -}, { - expected: [0, 255, 0, 0, 255, 0], - type: "", - desc: "Array with two buffers" -}); - -test_blob_binary(function() { - var view = new Uint8Array([0, 255, 0, 4]); - var blob = new Blob([view, view]); - assert_equals(blob.size, 8); - var view1 = new Uint16Array(view.buffer, 2); - return new Blob([view1, view.buffer, view1]); -}, { - expected: [0, 4, 0, 255, 0, 4, 0, 4], - type: "", - desc: "Array with two bufferviews" -}); - -test_blob(function() { - var view = new Uint8Array([0]); - var blob = new Blob(["fo"]); - return new Blob([view.buffer, blob, "foo"]); -}, { - expected: "\0fofoo", - type: "", - desc: "Array with mixed types" -}); - -test(function() { - const accessed = []; - const stringified = []; - - new Blob([], { - get type() { accessed.push('type'); }, - get endings() { accessed.push('endings'); } - }); - new Blob([], { - type: { toString: () => { stringified.push('type'); return ''; } }, - endings: { toString: () => { stringified.push('endings'); return 'transparent'; } } - }); - assert_array_equals(accessed, ['endings', 'type']); - assert_array_equals(stringified, ['endings', 'type']); -}, "options properties should be accessed in lexicographic order."); - -test(function() { - assert_throws_exactly(test_error, function() { - new Blob( - [{ toString: function() { throw test_error } }], - { - get type() { assert_unreached("type getter should not be called."); } - } - ); - }); -}, "Arguments should be evaluated from left to right."); - -[ - null, - undefined, - {}, - { unrecognized: true }, - /regex/, - function() {} -].forEach(function(arg, idx) { - test_blob(function() { - return new Blob([], arg); - }, { - expected: "", - type: "", - desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults." - }); - test_blob(function() { - return new Blob(["\na\r\nb\n\rc\r"], arg); - }, { - expected: "\na\r\nb\n\rc\r", - type: "", - desc: "Passing " + format_value(arg) + " (index " + idx + ") for options should use the defaults (with newlines)." - }); -}); - -[ - 123, - 123.4, - true, - 'abc' -].forEach(arg => { - test(t => { - assert_throws_js(TypeError, () => new Blob([], arg), - 'Blob constructor should throw with invalid property bag'); - }, `Passing ${JSON.stringify(arg)} for options should throw`); -}); - -var type_tests = [ - // blobParts, type, expected type - [[], '', ''], - [[], 'a', 'a'], - [[], 'A', 'a'], - [[], 'text/html', 'text/html'], - [[], 'TEXT/HTML', 'text/html'], - [[], 'text/plain;charset=utf-8', 'text/plain;charset=utf-8'], - [[], '\u00E5', ''], - [[], '\uD801\uDC7E', ''], // U+1047E - [[], ' image/gif ', ' image/gif '], - [[], '\timage/gif\t', ''], - [[], 'image/gif;\u007f', ''], - [[], '\u0130mage/gif', ''], // uppercase i with dot - [[], '\u0131mage/gif', ''], // lowercase dotless i - [[], 'image/gif\u0000', ''], - // check that type isn't changed based on sniffing - [[0x3C, 0x48, 0x54, 0x4D, 0x4C, 0x3E], 'unknown/unknown', 'unknown/unknown'], // "<HTML>" - [[0x00, 0xFF], 'text/plain', 'text/plain'], - [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], 'image/png', 'image/png'], // "GIF89a" -]; - -type_tests.forEach(function(t) { - test(function() { - var arr = new Uint8Array([t[0]]).buffer; - var b = new Blob([arr], {type:t[1]}); - assert_equals(b.type, t[2]); - }, "Blob with type " + format_value(t[1])); -}); -</script> diff --git a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice-overflow.any.js b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice-overflow.any.js new file mode 100644 index 00000000000..388fd9282c9 --- /dev/null +++ b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice-overflow.any.js @@ -0,0 +1,32 @@ +// META: title=Blob slice overflow +'use strict'; + +var text = ''; + +for (var i = 0; i < 2000; ++i) { + text += 'A'; +} + +test(function() { + var blob = new Blob([text]); + var sliceBlob = blob.slice(-1, blob.size); + assert_equals(sliceBlob.size, 1, "Blob slice size"); +}, "slice start is negative, relativeStart will be max((size + start), 0)"); + +test(function() { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size + 1, blob.size); + assert_equals(sliceBlob.size, 0, "Blob slice size"); +}, "slice start is greater than blob size, relativeStart will be min(start, size)"); + +test(function() { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size - 2, -1); + assert_equals(sliceBlob.size, 1, "Blob slice size"); +}, "slice end is negative, relativeEnd will be max((size + end), 0)"); + +test(function() { + var blob = new Blob([text]); + var sliceBlob = blob.slice(blob.size - 2, blob.size + 999); + assert_equals(sliceBlob.size, 2, "Blob slice size"); +}, "slice end is greater than blob size, relativeEnd will be min(end, size)"); diff --git a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice-overflow.html b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice-overflow.html deleted file mode 100644 index 74cd83a34f7..00000000000 --- a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice-overflow.html +++ /dev/null @@ -1,42 +0,0 @@ -<!DOCTYPE html> -<meta charset="utf-8"> -<title>Blob slice overflow</title> -<link rel="author" title="Intel" href="http://www.intel.com"> -<link rel="help" href="https://w3c.github.io/FileAPI/#dfn-slice"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<div id="log"></div> -<script> - -var text = ''; - -for (var i = 0; i < 2000; ++i) { - text += 'A'; -} - -test(function() { - var blob = new Blob([text]); - var sliceBlob = blob.slice(-1, blob.size); - assert_equals(sliceBlob.size, 1, "Blob slice size"); -}, "slice start is negative, relativeStart will be max((size + start), 0)"); - -test(function() { - var blob = new Blob([text]); - var sliceBlob = blob.slice(blob.size + 1, blob.size); - assert_equals(sliceBlob.size, 0, "Blob slice size"); -}, "slice start is greater than blob size, relativeStart will be min(start, size)"); - -test(function() { - var blob = new Blob([text]); - var sliceBlob = blob.slice(blob.size - 2, -1); - assert_equals(sliceBlob.size, 1, "Blob slice size"); -}, "slice end is negative, relativeEnd will be max((size + end), 0)"); - -test(function() { - var blob = new Blob([text]); - var sliceBlob = blob.slice(blob.size - 2, blob.size + 999); - assert_equals(sliceBlob.size, 2, "Blob slice size"); -}, "slice end is greater than blob size, relativeEnd will be min(end, size)"); - -</script> - diff --git a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice.any.js b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice.any.js new file mode 100644 index 00000000000..1f85d44d269 --- /dev/null +++ b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice.any.js @@ -0,0 +1,231 @@ +// META: title=Blob slice +// META: script=../support/Blob.js +'use strict'; + +test_blob(function() { + var blobTemp = new Blob(["PASS"]); + return blobTemp.slice(); +}, { + expected: "PASS", + type: "", + desc: "no-argument Blob slice" +}); + +test(function() { + var blob1, blob2; + + test_blob(function() { + return blob1 = new Blob(["squiggle"]); + }, { + expected: "squiggle", + type: "", + desc: "blob1." + }); + + test_blob(function() { + return blob2 = new Blob(["steak"], {type: "content/type"}); + }, { + expected: "steak", + type: "content/type", + desc: "blob2." + }); + + test_blob(function() { + return new Blob().slice(0,0,null); + }, { + expected: "", + type: "null", + desc: "null type Blob slice" + }); + + test_blob(function() { + return new Blob().slice(0,0,undefined); + }, { + expected: "", + type: "", + desc: "undefined type Blob slice" + }); + + test_blob(function() { + return new Blob().slice(0,0); + }, { + expected: "", + type: "", + desc: "no type Blob slice" + }); + + var arrayBuffer = new ArrayBuffer(16); + var int8View = new Int8Array(arrayBuffer); + for (var i = 0; i < 16; i++) { + int8View[i] = i + 65; + } + + var testData = [ + [ + ["PASSSTRING"], + [{start: -6, contents: "STRING"}, + {start: -12, contents: "PASSSTRING"}, + {start: 4, contents: "STRING"}, + {start: 12, contents: ""}, + {start: 0, end: -6, contents: "PASS"}, + {start: 0, end: -12, contents: ""}, + {start: 0, end: 4, contents: "PASS"}, + {start: 0, end: 12, contents: "PASSSTRING"}, + {start: 7, end: 4, contents: ""}] + ], + + // Test 3 strings + [ + ["foo", "bar", "baz"], + [{start: 0, end: 9, contents: "foobarbaz"}, + {start: 0, end: 3, contents: "foo"}, + {start: 3, end: 9, contents: "barbaz"}, + {start: 6, end: 9, contents: "baz"}, + {start: 6, end: 12, contents: "baz"}, + {start: 0, end: 9, contents: "foobarbaz"}, + {start: 0, end: 11, contents: "foobarbaz"}, + {start: 10, end: 15, contents: ""}] + ], + + // Test string, Blob, string + [ + ["foo", blob1, "baz"], + [{start: 0, end: 3, contents: "foo"}, + {start: 3, end: 11, contents: "squiggle"}, + {start: 2, end: 4, contents: "os"}, + {start: 10, end: 12, contents: "eb"}] + ], + + // Test blob, string, blob + [ + [blob1, "foo", blob1], + [{start: 0, end: 8, contents: "squiggle"}, + {start: 7, end: 9, contents: "ef"}, + {start: 10, end: 12, contents: "os"}, + {start: 1, end: 4, contents: "qui"}, + {start: 12, end: 15, contents: "qui"}, + {start: 40, end: 60, contents: ""}] + ], + + // Test blobs all the way down + [ + [blob2, blob1, blob2], + [{start: 0, end: 5, contents: "steak"}, + {start: 5, end: 13, contents: "squiggle"}, + {start: 13, end: 18, contents: "steak"}, + {start: 1, end: 3, contents: "te"}, + {start: 6, end: 10, contents: "quig"}] + ], + + // Test an ArrayBufferView + [ + [int8View, blob1, "foo"], + [{start: 0, end: 8, contents: "ABCDEFGH"}, + {start: 8, end: 18, contents: "IJKLMNOPsq"}, + {start: 17, end: 20, contents: "qui"}, + {start: 4, end: 12, contents: "EFGHIJKL"}] + ], + + // Test a partial ArrayBufferView + [ + [new Uint8Array(arrayBuffer, 3, 5), blob1, "foo"], + [{start: 0, end: 8, contents: "DEFGHsqu"}, + {start: 8, end: 18, contents: "igglefoo"}, + {start: 4, end: 12, contents: "Hsquiggl"}] + ], + + // Test type coercion of a number + [ + [3, int8View, "foo"], + [{start: 0, end: 8, contents: "3ABCDEFG"}, + {start: 8, end: 18, contents: "HIJKLMNOPf"}, + {start: 17, end: 21, contents: "foo"}, + {start: 4, end: 12, contents: "DEFGHIJK"}] + ], + + [ + [(new Uint8Array([0, 255, 0])).buffer, + new Blob(['abcd']), + 'efgh', + 'ijklmnopqrstuvwxyz'], + [{start: 1, end: 4, contents: "\uFFFD\u0000a"}, + {start: 4, end: 8, contents: "bcde"}, + {start: 8, end: 12, contents: "fghi"}, + {start: 1, end: 12, contents: "\uFFFD\u0000abcdefghi"}] + ] + ]; + + testData.forEach(function(data, i) { + var blobs = data[0]; + var tests = data[1]; + tests.forEach(function(expectations, j) { + test(function() { + var blob = new Blob(blobs); + assert_true(blob instanceof Blob); + assert_false(blob instanceof File); + + test_blob(function() { + return expectations.end === undefined + ? blob.slice(expectations.start) + : blob.slice(expectations.start, expectations.end); + }, { + expected: expectations.contents, + type: "", + desc: "Slicing test: slice (" + i + "," + j + ")." + }); + }, "Slicing test (" + i + "," + j + ")."); + }); + }); +}, "Slices"); + +var invalidTypes = [ + "\xFF", + "te\x09xt/plain", + "te\x00xt/plain", + "te\x1Fxt/plain", + "te\x7Fxt/plain" +]; +invalidTypes.forEach(function(type) { + test_blob(function() { + var blob = new Blob(["PASS"]); + return blob.slice(0, 4, type); + }, { + expected: "PASS", + type: "", + desc: "Invalid contentType (" + format_value(type) + ")" + }); +}); + +var validTypes = [ + "te(xt/plain", + "te)xt/plain", + "te<xt/plain", + "te>xt/plain", + "te@xt/plain", + "te,xt/plain", + "te;xt/plain", + "te:xt/plain", + "te\\xt/plain", + "te\"xt/plain", + "te/xt/plain", + "te[xt/plain", + "te]xt/plain", + "te?xt/plain", + "te=xt/plain", + "te{xt/plain", + "te}xt/plain", + "te\x20xt/plain", + "TEXT/PLAIN", + "text/plain;charset = UTF-8", + "text/plain;charset=UTF-8" +]; +validTypes.forEach(function(type) { + test_blob(function() { + var blob = new Blob(["PASS"]); + return blob.slice(0, 4, type); + }, { + expected: "PASS", + type: type.toLowerCase(), + desc: "Valid contentType (" + format_value(type) + ")" + }); +}); diff --git a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice.html b/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice.html deleted file mode 100644 index 03fe6ca5343..00000000000 --- a/tests/wpt/web-platform-tests/FileAPI/blob/Blob-slice.html +++ /dev/null @@ -1,238 +0,0 @@ -<!DOCTYPE html> -<meta charset=utf-8> -<title>Blob slice</title> -<link rel=help href="https://w3c.github.io/FileAPI/#slice-method-algo"> -<link rel=author title="Saurabh Anand" href="mailto:saurabhanandiit@gmail.com"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="../support/Blob.js"></script> -<div id="log"></div> -<script> -test_blob(function() { - var blobTemp = new Blob(["PASS"]); - return blobTemp.slice(); -}, { - expected: "PASS", - type: "", - desc: "no-argument Blob slice" -}); - -test(function() { - var blob1, blob2; - - test_blob(function() { - return blob1 = new Blob(["squiggle"]); - }, { - expected: "squiggle", - type: "", - desc: "blob1." - }); - - test_blob(function() { - return blob2 = new Blob(["steak"], {type: "content/type"}); - }, { - expected: "steak", - type: "content/type", - desc: "blob2." - }); - - test_blob(function() { - return new Blob().slice(0,0,null); - }, { - expected: "", - type: "null", - desc: "null type Blob slice" - }); - - test_blob(function() { - return new Blob().slice(0,0,undefined); - }, { - expected: "", - type: "", - desc: "undefined type Blob slice" - }); - - test_blob(function() { - return new Blob().slice(0,0); - }, { - expected: "", - type: "", - desc: "no type Blob slice" - }); - - var arrayBuffer = new ArrayBuffer(16); - var int8View = new Int8Array(arrayBuffer); - for (var i = 0; i < 16; i++) { - int8View[i] = i + 65; - } - - var testData = [ - [ - ["PASSSTRING"], - [{start: -6, contents: "STRING"}, - {start: -12, contents: "PASSSTRING"}, - {start: 4, contents: "STRING"}, - {start: 12, contents: ""}, - {start: 0, end: -6, contents: "PASS"}, - {start: 0, end: -12, contents: ""}, - {start: 0, end: 4, contents: "PASS"}, - {start: 0, end: 12, contents: "PASSSTRING"}, - {start: 7, end: 4, contents: ""}] - ], - - // Test 3 strings - [ - ["foo", "bar", "baz"], - [{start: 0, end: 9, contents: "foobarbaz"}, - {start: 0, end: 3, contents: "foo"}, - {start: 3, end: 9, contents: "barbaz"}, - {start: 6, end: 9, contents: "baz"}, - {start: 6, end: 12, contents: "baz"}, - {start: 0, end: 9, contents: "foobarbaz"}, - {start: 0, end: 11, contents: "foobarbaz"}, - {start: 10, end: 15, contents: ""}] - ], - - // Test string, Blob, string - [ - ["foo", blob1, "baz"], - [{start: 0, end: 3, contents: "foo"}, - {start: 3, end: 11, contents: "squiggle"}, - {start: 2, end: 4, contents: "os"}, - {start: 10, end: 12, contents: "eb"}] - ], - - // Test blob, string, blob - [ - [blob1, "foo", blob1], - [{start: 0, end: 8, contents: "squiggle"}, - {start: 7, end: 9, contents: "ef"}, - {start: 10, end: 12, contents: "os"}, - {start: 1, end: 4, contents: "qui"}, - {start: 12, end: 15, contents: "qui"}, - {start: 40, end: 60, contents: ""}] - ], - - // Test blobs all the way down - [ - [blob2, blob1, blob2], - [{start: 0, end: 5, contents: "steak"}, - {start: 5, end: 13, contents: "squiggle"}, - {start: 13, end: 18, contents: "steak"}, - {start: 1, end: 3, contents: "te"}, - {start: 6, end: 10, contents: "quig"}] - ], - - // Test an ArrayBufferView - [ - [int8View, blob1, "foo"], - [{start: 0, end: 8, contents: "ABCDEFGH"}, - {start: 8, end: 18, contents: "IJKLMNOPsq"}, - {start: 17, end: 20, contents: "qui"}, - {start: 4, end: 12, contents: "EFGHIJKL"}] - ], - - // Test a partial ArrayBufferView - [ - [new Uint8Array(arrayBuffer, 3, 5), blob1, "foo"], - [{start: 0, end: 8, contents: "DEFGHsqu"}, - {start: 8, end: 18, contents: "igglefoo"}, - {start: 4, end: 12, contents: "Hsquiggl"}] - ], - - // Test type coercion of a number - [ - [3, int8View, "foo"], - [{start: 0, end: 8, contents: "3ABCDEFG"}, - {start: 8, end: 18, contents: "HIJKLMNOPf"}, - {start: 17, end: 21, contents: "foo"}, - {start: 4, end: 12, contents: "DEFGHIJK"}] - ], - - [ - [(new Uint8Array([0, 255, 0])).buffer, - new Blob(['abcd']), - 'efgh', - 'ijklmnopqrstuvwxyz'], - [{start: 1, end: 4, contents: "\uFFFD\u0000a"}, - {start: 4, end: 8, contents: "bcde"}, - {start: 8, end: 12, contents: "fghi"}, - {start: 1, end: 12, contents: "\uFFFD\u0000abcdefghi"}] - ] - ]; - - testData.forEach(function(data, i) { - var blobs = data[0]; - var tests = data[1]; - tests.forEach(function(expectations, j) { - test(function() { - var blob = new Blob(blobs); - assert_true(blob instanceof Blob); - assert_false(blob instanceof File); - - test_blob(function() { - return expectations.end === undefined - ? blob.slice(expectations.start) - : blob.slice(expectations.start, expectations.end); - }, { - expected: expectations.contents, - type: "", - desc: "Slicing test: slice (" + i + "," + j + ")." - }); - }, "Slicing test (" + i + "," + j + ")."); - }); - }); -}, "Slices"); - -var invalidTypes = [ - "\xFF", - "te\x09xt/plain", - "te\x00xt/plain", - "te\x1Fxt/plain", - "te\x7Fxt/plain" -]; -invalidTypes.forEach(function(type) { - test_blob(function() { - var blob = new Blob(["PASS"]); - return blob.slice(0, 4, type); - }, { - expected: "PASS", - type: "", - desc: "Invalid contentType (" + format_value(type) + ")" - }); -}); - -var validTypes = [ - "te(xt/plain", - "te)xt/plain", - "te<xt/plain", - "te>xt/plain", - "te@xt/plain", - "te,xt/plain", - "te;xt/plain", - "te:xt/plain", - "te\\xt/plain", - "te\"xt/plain", - "te/xt/plain", - "te[xt/plain", - "te]xt/plain", - "te?xt/plain", - "te=xt/plain", - "te{xt/plain", - "te}xt/plain", - "te\x20xt/plain", - "TEXT/PLAIN", - "text/plain;charset = UTF-8", - "text/plain;charset=UTF-8" -]; -validTypes.forEach(function(type) { - test_blob(function() { - var blob = new Blob(["PASS"]); - return blob.slice(0, 4, type); - }, { - expected: "PASS", - type: type.toLowerCase(), - desc: "Valid contentType (" + format_value(type) + ")" - }); -}); -</script> diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail-manual.sub.html new file mode 100644 index 00000000000..a6acdd4b014 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail-manual.sub.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>Test that icons member is supported (CORS violation)</title> +<link rel="help" href="https://w3c.github.io/manifest#icons-member" /> +<link rel="manifest" href="icons-member-cors-fail.sub.webmanifest" /> +<h1>Testing support for icons member (CORS violation)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the icon <strong>must not</strong> show a white cross on a red background. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail.sub.webmanifest b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail.sub.webmanifest new file mode 100644 index 00000000000..e8b9327ed8f --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail.sub.webmanifest @@ -0,0 +1,7 @@ +{ + "icons": [{ + "src": "https://{{host}}:{{ports[https][1]}}/appmanifest/icons-member/fail.png", + "sizes": "256x256", + "type": "image/png" + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-fail.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-manual.sub.html new file mode 100644 index 00000000000..a54f3629caf --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors-manual.sub.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>Test that icons member is supported (via CORS)</title> +<link rel="help" href="https://w3c.github.io/manifest#icons-member" /> +<link rel="manifest" href="icons-member-cors.sub.webmanifest" /> +<h1>Testing support for icons member (via CORS)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the icon must show a white check mark on a green background. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors.sub.webmanifest b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors.sub.webmanifest new file mode 100644 index 00000000000..5d95262af3e --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors.sub.webmanifest @@ -0,0 +1,7 @@ +{ + "icons": [{ + "src": "https://{{host}}:{{ports[https][1]}}/appmanifest/icons-member/pass.png", + "sizes": "256x256", + "type": "image/png" + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-cors.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail-manual.sub.html new file mode 100644 index 00000000000..7f35fc318e8 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail-manual.sub.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="img-src {{host}}:{{ports[https][1]}}"> +<title>Test that icons member is supported (CSP violation)</title> +<link rel="help" href="https://w3c.github.io/manifest#icons-member" /> +<link rel="manifest" href="icons-member-csp-fail.webmanifest" /> +<h1>Testing support for icons member (CSP violation)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the icon <strong>must not</strong> show a white cross on a red background. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail.webmanifest b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail.webmanifest new file mode 100644 index 00000000000..a17364871ec --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail.webmanifest @@ -0,0 +1,7 @@ +{ + "icons": [{ + "src": "fail.png", + "sizes": "256x256", + "type": "image/png" + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-fail.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-manual.sub.html new file mode 100644 index 00000000000..15e09129e97 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp-manual.sub.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="img-src {{host}}:{{ports[https][1]}}"> +<title>Test that icons member is supported (CSP check)</title> +<link rel="help" href="https://w3c.github.io/manifest#icons-member" /> +<link rel="manifest" href="icons-member-csp.sub.webmanifest" /> +<h1>Testing support for icons member (CSP check)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the icon must show a white check mark on a green background. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp.sub.webmanifest b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp.sub.webmanifest new file mode 100644 index 00000000000..5d95262af3e --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp.sub.webmanifest @@ -0,0 +1,7 @@ +{ + "icons": [{ + "src": "https://{{host}}:{{ports[https][1]}}/appmanifest/icons-member/pass.png", + "sizes": "256x256", + "type": "image/png" + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/icons-member-csp.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/appmanifest/icons-member/pass.png.sub.headers b/tests/wpt/web-platform-tests/appmanifest/icons-member/pass.png.sub.headers new file mode 100644 index 00000000000..adf190aa8ea --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/icons-member/pass.png.sub.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: https://{{host}}:{{ports[https][0]}} diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/pass.png.sub.headers b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/pass.png.sub.headers new file mode 100644 index 00000000000..adf190aa8ea --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/pass.png.sub.headers @@ -0,0 +1 @@ +Access-Control-Allow-Origin: https://{{host}}:{{ports[https][0]}} diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail-manual.sub.html new file mode 100644 index 00000000000..06ea5aff1b4 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail-manual.sub.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<title>Test that shortcuts member is supported (icon violates CORS)</title> +<link rel="help" href="https://w3c.github.io/manifest/#shortcuts-member" /> +<link rel="manifest" href="shortcuts-member-cors-fail.sub.webmanifest" /> +<h1>Testing support for shortcuts member (icon violates CORS)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the application <strong>must not</strong> show a shortcut with + a white cross on red background. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail.sub.webmanifest b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail.sub.webmanifest new file mode 100644 index 00000000000..3edb9aa77de --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail.sub.webmanifest @@ -0,0 +1,14 @@ +{ + "name": "Shortcut test", + "scope": "/", + "shortcuts": [{ + "name": "pass", + "short_name": "", + "description": "", + "url": "shortcut_pass.html", + "icons": [{ + "src": "https://{{host}}:{{ports[https][1]}}/appmanifest/icons-member/fail.png", + "sizes": "256x256" + }] + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-fail.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-manual.sub.html new file mode 100644 index 00000000000..a4fc0160c85 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors-manual.sub.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<title>Test that shortcuts member is supported (icon via CORS)</title> +<link rel="help" href="https://w3c.github.io/manifest/#shortcuts-member" /> +<link rel="manifest" href="shortcuts-member-cors.sub.webmanifest" /> +<h1>Testing support for shortcuts member (icon via CORS)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the application must show a shortcut named "pass" with a check + mark icon. Clicking it must open this document again. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors.sub.webmanifest b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors.sub.webmanifest new file mode 100644 index 00000000000..9e17c1898c4 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors.sub.webmanifest @@ -0,0 +1,14 @@ +{ + "name": "Shortcut test", + "scope": "/", + "shortcuts": [{ + "name": "pass", + "short_name": "", + "description": "", + "url": "shortcut_pass.html", + "icons": [{ + "src": "https://{{host}}:{{ports[https][1]}}/appmanifest/icons-member/pass.png", + "sizes": "256x256" + }] + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-cors.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail-manual.sub.html new file mode 100644 index 00000000000..60a2601286c --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail-manual.sub.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="img-src {{host}}:{{ports[https][1]}}"> +<title>Test that shortcuts member is supported (icon CSP violation)</title> +<link rel="help" href="https://w3c.github.io/manifest/#shortcuts-member" /> +<link rel="manifest" href="shortcuts-member-cors-fail.sub.webmanifest" /> +<h1>Testing support for shortcuts member (icon CSP violation)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the application <strong>must not</strong> show a shortcut with + a white cross on red background. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail.webmanifest b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail.webmanifest new file mode 100644 index 00000000000..47f15dd818d --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail.webmanifest @@ -0,0 +1,14 @@ +{ + "name": "Shortcut test", + "scope": "/", + "shortcuts": [{ + "name": "pass", + "short_name": "", + "description": "", + "url": "shortcut_pass.html", + "icons": [{ + "src": "fail.png", + "sizes": "256x256" + }] + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-fail.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-manual.sub.html b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-manual.sub.html new file mode 100644 index 00000000000..09cb58f4a97 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp-manual.sub.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="img-src {{host}}:{{ports[https][1]}}"> +<title>Test that shortcuts member is supported (icon CSP check)</title> +<link rel="help" href="https://w3c.github.io/manifest/#shortcuts-member" /> +<link rel="manifest" href="shortcuts-member-cors.sub.webmanifest" /> +<h1>Testing support for shortcuts member (icon CSP check)</h1> +<script> + // Force the port of the origin to be ports[https][0] (likely :8443) + // we treat the port ports[https][1] (likely :8444) to be another origin that we fail against + if (window.location.origin !== "https://{{host}}:{{ports[https][0]}}") { + window.location = new URL(window.location.pathname, "https://{{host}}:{{ports[https][0]}}") + } +</script> +<p> + To pass, the application must show a shortcut named "pass" with a check + mark icon. Clicking it must open this document again. +</p> diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp.sub.webmanifest b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp.sub.webmanifest new file mode 100644 index 00000000000..9e17c1898c4 --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp.sub.webmanifest @@ -0,0 +1,14 @@ +{ + "name": "Shortcut test", + "scope": "/", + "shortcuts": [{ + "name": "pass", + "short_name": "", + "description": "", + "url": "shortcut_pass.html", + "icons": [{ + "src": "https://{{host}}:{{ports[https][1]}}/appmanifest/icons-member/pass.png", + "sizes": "256x256" + }] + }] +} diff --git a/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp.webmanifest.headers b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp.webmanifest.headers new file mode 100644 index 00000000000..2bab061d43a --- /dev/null +++ b/tests/wpt/web-platform-tests/appmanifest/shortcuts-member/shortcuts-member-csp.webmanifest.headers @@ -0,0 +1 @@ +Content-Type: application/manifest+json; charset=utf-8 diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-blob.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-blob.html deleted file mode 100644 index 805ea63be25..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-blob.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic Blob Test</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(blobTests); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-blobMax.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-blobMax.html deleted file mode 100644 index e18b163d630..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-blobMax.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic Blob Test - MaxSize</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(blobMaxTest); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-buffersource.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-buffersource.html deleted file mode 100644 index 14c7a4f30f8..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-buffersource.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic BufferSource Test</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(bufferSourceTests); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-buffersourceMax.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-buffersourceMax.html deleted file mode 100644 index 5163ecd75a9..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-buffersourceMax.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic BufferSource Test - MaxSize</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(bufferSourceMaxTest); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-formdata.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-formdata.html deleted file mode 100644 index 9f46cf6a793..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-formdata.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic FormData Test</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(formDataTests); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-formdataMax.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-formdataMax.html deleted file mode 100644 index 2c7e0fa805c..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-formdataMax.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic FormData Test</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(formDataMaxTest); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-string.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-string.html deleted file mode 100644 index cba65dade98..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-string.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic String Test</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(stringTests); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic-stringMax.html b/tests/wpt/web-platform-tests/beacon/beacon-basic-stringMax.html deleted file mode 100644 index 005e9f5ce88..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-basic-stringMax.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Basic String Test - MaxSize</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - runTests(stringMaxTest); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-basic.https.window.js b/tests/wpt/web-platform-tests/beacon/beacon-basic.https.window.js new file mode 100644 index 00000000000..47117716a28 --- /dev/null +++ b/tests/wpt/web-platform-tests/beacon/beacon-basic.https.window.js @@ -0,0 +1,98 @@ +// META: timeout=long +// META: script=/common/utils.js +// META: script=beacon-common.sub.js + +'use strict'; + +// TODO(yhirano): Check the sec-fetch-mode request header once WebKit supports +// the feature. + +parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const id = token(); + const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`; + assert_true(iframe.contentWindow.navigator.sendBeacon(url)); + iframe.remove(); + + const result = await waitForResult(id); + assert_equals(result.type, '(missing)', 'content-type'); +}, `simple case: with no payload`); + +parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const id = token(); + const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`; + assert_true(iframe.contentWindow.navigator.sendBeacon(url, null)); + iframe.remove(); + + const result = await waitForResult(id); + assert_equals(result.type, '(missing)', 'content-type'); +}, `simple case: with null payload`); + +for (const size of [EMPTY, SMALL, LARGE, MAX]) { + for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) { + if (size === MAX && type === FORM) { + // It is difficult to estimate the size of a form accurately, so we cannot + // test this case. + continue; + } + parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(size, type); + const id = token(); + const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`; + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + iframe.remove(); + + const result = await waitForResult(id); + if (getContentType(type) === null) { + assert_equals(result.type, '(missing)', 'content-type'); + } else { + assert_true(result.type.includes(getContentType(type)), 'content-type'); + } + }, `simple case: type = ${type} and size = ${size}`); + } +} + +for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) { + parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(TOOLARGE, type); + const id = token(); + const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`; + assert_false(iframe.contentWindow.navigator.sendBeacon(url, payload)); + }, `Too large payload should be rejected: type = ${type}`); +} + +for (const type of [STRING, ARRAYBUFFER, BLOB]) { + parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + assert_true(iframe.contentWindow.navigator.sendBeacon( + `/beacon/resources/beacon.py?cmd=store&id=${token()}`, + makePayload(MAX, type))); + assert_true(iframe.contentWindow.navigator.sendBeacon( + `/beacon/resources/beacon.py?cmd=store&id=${token()}`, '')); + assert_false(iframe.contentWindow.navigator.sendBeacon( + `/beacon/resources/beacon.py?cmd=store&id=${token()}`, 'x')); + }, `Payload size restriction should be accumulated: type = ${type}`); +} + +test(() => { + assert_throws_js( + TypeError, () => navigator.sendBeacon('...', new ReadableStream())); +}, 'sendBeacon() with a stream does not work due to the keepalive flag being set'); diff --git a/tests/wpt/web-platform-tests/beacon/beacon-common.sub.js b/tests/wpt/web-platform-tests/beacon/beacon-common.sub.js index 279ad2229ba..46994954608 100644 --- a/tests/wpt/web-platform-tests/beacon/beacon-common.sub.js +++ b/tests/wpt/web-platform-tests/beacon/beacon-common.sub.js @@ -1,172 +1,111 @@ -"use strict"; - -// Different sizes of payloads to test. -var smallPayloadSize = 10; -var mediumPayloadSize = 10000; -var largePayloadSize = 50000; -var maxPayloadSize = 65536; // The maximum payload size allowed for a beacon request. - -// String payloads of various sizes sent by sendbeacon. The format of the payloads is a string: -// <numberOfCharacters>:<numberOfCharacters *'s> -// ex. "10:**********" -var smallPayload = smallPayloadSize + ":" + Array(smallPayloadSize).fill('*').join(""); -var mediumPayload = mediumPayloadSize + ":" + Array(mediumPayloadSize).fill('*').join(""); -var largePayload = largePayloadSize + ":" + Array(largePayloadSize).fill('*').join(""); -// Subtract 6 from maxPayloadSize because 65536 is 5 digits, plus 1 more for the ':' -var maxPayload = (maxPayloadSize - 6) + ":" + Array(maxPayloadSize - 6).fill('*').join("") - -// Test case definitions. -// name: String containing the unique name of the test case. -// data: Payload object to send through sendbeacon. -var noDataTest = { name: "NoData" }; -var nullDataTest = { name: "NullData", data: null }; -var undefinedDataTest = { name: "UndefinedData", data: undefined }; -var smallStringTest = { name: "SmallString", data: smallPayload }; -var mediumStringTest = { name: "MediumString", data: mediumPayload }; -var largeStringTest = { name: "LargeString", data: largePayload }; -var maxStringTest = { name: "MaxString", data: maxPayload }; -var emptyBlobTest = { name: "EmptyBlob", data: new Blob() }; -var smallBlobTest = { name: "SmallBlob", data: new Blob([smallPayload]) }; -var mediumBlobTest = { name: "MediumBlob", data: new Blob([mediumPayload]) }; -var largeBlobTest = { name: "LargeBlob", data: new Blob([largePayload]) }; -var maxBlobTest = { name: "MaxBlob", data: new Blob([maxPayload]) }; -var emptyBufferSourceTest = { name: "EmptyBufferSource", data: new Uint8Array() }; -var smallBufferSourceTest = { name: "SmallBufferSource", data: CreateArrayBufferFromPayload(smallPayload) }; -var mediumBufferSourceTest = { name: "MediumBufferSource", data: CreateArrayBufferFromPayload(mediumPayload) }; -var largeBufferSourceTest = { name: "LargeBufferSource", data: CreateArrayBufferFromPayload(largePayload) }; -var maxBufferSourceTest = { name: "MaxBufferSource", data: CreateArrayBufferFromPayload(maxPayload) }; -var emptyFormDataTest = { name: "EmptyFormData", data: CreateEmptyFormDataPayload() }; -var smallFormDataTest = { name: "SmallFormData", data: CreateFormDataFromPayload(smallPayload) }; -var mediumFormDataTest = { name: "MediumFormData", data: CreateFormDataFromPayload(mediumPayload) }; -var largeFormDataTest = { name: "LargeFormData", data: CreateFormDataFromPayload(largePayload) }; -var smallSafeContentTypeEncodedTest = { name: "SmallSafeContentTypeEncoded", data: new Blob([smallPayload], { type: 'application/x-www-form-urlencoded' }) }; -var smallSafeContentTypeFormTest = { name: "SmallSafeContentTypeForm", data: new FormData() }; -var smallSafeContentTypeTextTest = { name: "SmallSafeContentTypeText", data: new Blob([smallPayload], { type: 'text/plain' }) }; -var smallCORSContentTypeTextTest = { name: "SmallCORSContentTypeText", data: new Blob([smallPayload], { type: 'text/html' }) }; -// We don't test maxFormData because the extra multipart separators make it difficult to -// calculate a maxPayload. - -// Test case suites. -// Due to quota limits we split the max payload tests into their own bucket. -var stringTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, mediumStringTest, largeStringTest]; -var stringMaxTest = [maxStringTest]; -var blobTests = [emptyBlobTest, smallBlobTest, mediumBlobTest, largeBlobTest]; -var blobMaxTest = [maxBlobTest]; -var bufferSourceTests = [emptyBufferSourceTest, smallBufferSourceTest, mediumBufferSourceTest, largeBufferSourceTest]; -var bufferSourceMaxTest = [maxBufferSourceTest]; -var formDataTests = [emptyFormDataTest, smallFormDataTest, mediumFormDataTest, largeFormDataTest]; -var formDataMaxTest = [largeFormDataTest]; -var contentTypeTests = [smallSafeContentTypeEncodedTest,smallSafeContentTypeFormTest,smallSafeContentTypeTextTest,smallCORSContentTypeTextTest]; -var allTests = [].concat(stringTests, stringMaxTest, blobTests, blobMaxTest, bufferSourceTests, bufferSourceMaxTest, formDataTests, formDataMaxTest, contentTypeTests); - -// This special cross section of test cases is meant to provide a slimmer but reasonably- -// representative set of tests for parameterization across variables (e.g. redirect codes, -// cors modes, etc.) -var sampleTests = [noDataTest, nullDataTest, undefinedDataTest, smallStringTest, smallBlobTest, smallBufferSourceTest, smallFormDataTest, smallSafeContentTypeEncodedTest, smallSafeContentTypeFormTest, smallSafeContentTypeTextTest]; - -var preflightTests = [smallCORSContentTypeTextTest]; - -// Helper function to create an ArrayBuffer representation of a string. -function CreateArrayBufferFromPayload(payload) { - var length = payload.length; - var buffer = new Uint8Array(length); - - for (var i = 0; i < length; i++) { - buffer[i] = payload.charCodeAt(i); - } - - return buffer; -} - -// Helper function to create an empty FormData object. -function CreateEmptyFormDataPayload() { - if (self.document === undefined) { - return null; - } - - return new FormData(); -} - -// Helper function to create a FormData representation of a string. -function CreateFormDataFromPayload(payload) { - if (self.document === undefined) { - return null; - } - - var formData = new FormData(); - formData.append("payload", payload); - return formData; +'use strict'; + +const EMPTY = 'empty'; +const SMALL = 'small'; +const LARGE = 'large'; +const MAX = 'max'; +const TOOLARGE = 'toolarge'; + +const STRING = 'string'; +const ARRAYBUFFER = 'arraybuffer'; +const FORM = 'form'; +const BLOB = 'blob'; + +function getContentType(type) { + switch (type) { + case STRING: + return 'text/plain;charset=UTF-8'; + case ARRAYBUFFER: + return null; + case FORM: + return 'multipart/form-data'; + case BLOB: + return null; + default: + throw Error(`invalid type: ${type}`); + } } -// Schedules promise_test's for each of the test cases. -// Parameters: -// testCases: An array of test cases. -// suffix [optional]: A string used for the suffix for each test case name. -// buildUrl [optional]: A function that returns a beacon URL given an id. -// sendData [optional]: A function that sends the beacon with given a URL and payload. -function runTests(testCases, suffix = '', buildUrl = self.buildUrl, sendData = self.sendData) { - for (const testCase of testCases) { - const id = token(); - promise_test((test) => { - const url = buildUrl(id); - assert_true(sendData(url, testCase.data), 'sendBeacon should succeed'); - return waitForResult(id); - }, `Verify 'navigator.sendbeacon()' successfully sends for variant: ${testCase.name}${suffix}`); - }; +// Create a payload with the given size and type. +// `sizeString` must be one of EMPTY, SMALL, LARGE, MAX, TOOLARGE. +// `type` must be one of STRING, ARRAYBUFFER, FORM, BLOB. +// `contentType` is effective only if `type` is BLOB. +function makePayload(sizeString, type, contentType) { + let size = 0; + switch (sizeString) { + case EMPTY: + size = 0; + break; + case SMALL: + size = 10; + break; + case LARGE: + size = 10 * 1000; + break; + case MAX: + if (type === FORM) { + throw Error('Not supported'); + } + size = 65536; + break; + case TOOLARGE: + size = 65537; + break; + default: + throw Error('invalid size'); + } + + let data = ''; + if (size > 0) { + const prefix = String(size) + ':'; + data = prefix + Array(size - prefix.length).fill('*').join(''); + } + + switch (type) { + case STRING: + return data; + case ARRAYBUFFER: + return new TextEncoder().encode(data).buffer; + case FORM: + const formData = new FormData(); + if (size > 0) { + formData.append('payload', data); + } + return formData; + case BLOB: + const options = contentType ? {type: contentType} : undefined; + const blob = new Blob([data], options); + return blob; + default: + throw Error('invalid type'); + } } -function buildUrl(id) { - const baseUrl = "http://{{host}}:{{ports[http][0]}}"; - return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}`; -} - -// Sends the beacon for a single test. This step is factored into its own function so that -// it can be called from a web worker. It does not check for results. -// Note: do not assert from this method, as when called from a worker, we won't have the -// full testharness.js test context. Instead return 'false', and the main scope will fail -// the test. -// Returns the result of the 'sendbeacon()' function call, true or false. -function sendData(url, payload) { - return self.navigator.sendBeacon(url, payload); +function parallelPromiseTest(func, description) { + async_test((t) => { + Promise.resolve(func(t)).then(() => t.done()).catch(t.step_func((e) => { + throw e; + })); + }, description); } // Poll the server for the test result. -async function waitForResult(id) { - const url = `resources/beacon.py?cmd=stat&id=${id}`; - for (let i = 0; i < 30; ++i) { - const response = await fetch(url); - const text = await response.text(); - const results = JSON.parse(text); - - if (results.length === 0) { - await new Promise(resolve => step_timeout(resolve, 100)); - continue; - } - assert_equals(results.length, 1, `bad response: '${text}'`);; - // null JSON values parse as null, not undefined - assert_equals(results[0].error, null, "'sendbeacon' data must not fail validation"); - return; +async function waitForResult(id, expectedError = null) { + const url = `/beacon/resources/beacon.py?cmd=stat&id=${id}`; + for (let i = 0; i < 30; ++i) { + const response = await fetch(url); + const text = await response.text(); + const results = JSON.parse(text); + + if (results.length === 0) { + await new Promise(resolve => step_timeout(resolve, 100)); + continue; } - assert_true(false, 'timeout'); -} - -// Creates an iframe on the document's body and runs the sample tests from the iframe. -// The iframe is navigated immediately after it sends the data, and the window verifies -// that the data is still successfully sent. -function runSendInIframeAndNavigateTests() { - var iframe = document.createElement("iframe"); - iframe.id = "iframe"; - iframe.onload = function() { - // Clear our onload handler to prevent re-running the tests as we navigate away. - iframe.onload = null; - function sendData(url, payload) { - return iframe.contentWindow.navigator.sendBeacon(url, payload); - } - runTests(sampleTests, '-NAVIGATE', self.buildUrl, sendData); - // Now navigate ourselves. - iframe.contentWindow.location = "http://{{host}}:{{ports[http][0]}}/"; - }; - - iframe.srcdoc = '<html></html>'; - document.body.appendChild(iframe); + assert_equals(results.length, 1, `bad response: '${text}'`); + const result = results[0]; + // null JSON values parse as null, not undefined + assert_equals(result.error, expectedError, 'error recorded in stash'); + return result; + } + assert_true(false, 'timeout'); } diff --git a/tests/wpt/web-platform-tests/beacon/beacon-cors.https.window.js b/tests/wpt/web-platform-tests/beacon/beacon-cors.https.window.js new file mode 100644 index 00000000000..6f282a23b15 --- /dev/null +++ b/tests/wpt/web-platform-tests/beacon/beacon-cors.https.window.js @@ -0,0 +1,132 @@ +// META: timeout=long +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=beacon-common.sub.js + +'use strict'; + +const {HTTPS_ORIGIN, ORIGIN, HTTPS_REMOTE_ORIGIN} = get_host_info(); + +// As /common/redirect.py is not under our control, let's make sure that +// it doesn't support CORS. +parallelPromiseTest(async (t) => { + const destination = `${HTTPS_REMOTE_ORIGIN}/common/text-plain.txt` + + `?pipe=header(access-control-allow-origin,*)`; + const redirect = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` + + `?location=${encodeURIComponent(destination)}`; + + // Fetching `destination` is fine because it supports CORS. + await fetch(destination); + + // Fetching redirect.py should fail because it doesn't support CORS. + await promise_rejects_js(t, TypeError, fetch(redirect)); +}, '/common/redirect.py does not support CORS'); + +for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) { + parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(SMALL, type); + const id = token(); + // As we use "no-cors" for CORS-safelisted requests, the redirect is + // processed without an error while the request is cross-origin and the + // redirect handler doesn't support CORS. + const destination = + `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`; + const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` + + `?status=307&location=${encodeURIComponent(destination)}`; + + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + iframe.remove(); + + await waitForResult(id); + }, `cross-origin, CORS-safelisted: type = ${type}`); +} + +parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); + const id = token(); + const destination = + `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`; + const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` + + `?status=307&location=${encodeURIComponent(destination)}`; + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + iframe.remove(); + + // The beacon is rejected during redirect handling because /common/redirect.py + // doesn't support CORS. + + await new Promise((resolve) => step_timeout(resolve, 3000)); + const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`); + assert_equals((await res.json()).length, 0); +}, `cross-origin, non-CORS-safelisted: failure case (with redirect)`); + +parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); + const id = token(); + const url = + `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`; + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + iframe.remove(); + + // The beacon is rejected during preflight handling. + await waitForResult(id, /*expectedError=*/ 'Preflight not expected.'); +}, `cross-origin, non-CORS-safelisted: failure case (without redirect)`); + +for (const credentials of [false, true]) { + parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); + const id = token(); + let url = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` + + `?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}`; + if (credentials) { + url += `&credentials=true`; + } + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + iframe.remove(); + + // We need access-control-allow-credentials in the preflight response. This + // shows that the request's credentials mode is 'include'. + if (credentials) { + const result = await waitForResult(id); + assert_equals(result.type, 'application/octet-stream'); + } else { + await new Promise((resolve) => step_timeout(resolve, 3000)); + const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`); + assert_equals((await res.json()).length, 0); + } + }, `cross-origin, non-CORS-safelisted[credentials=${credentials}]`); +} + +parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(SMALL, BLOB, 'application/octet-stream'); + const id = token(); + const destination = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` + + `?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}&credentials=true`; + const url = `${HTTPS_REMOTE_ORIGIN}/fetch/api/resources/redirect.py` + + `?redirect_status=307&allow_headers=content-type` + + `&location=${encodeURIComponent(destination)}`; + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + iframe.remove(); + + const result = await waitForResult(id); + assert_equals(result.type, 'application/octet-stream'); +}, `cross-origin, non-CORS-safelisted success-case (with redirect)`); diff --git a/tests/wpt/web-platform-tests/beacon/beacon-cors.sub.window.js b/tests/wpt/web-platform-tests/beacon/beacon-cors.sub.window.js deleted file mode 100644 index 5543bddc5f6..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-cors.sub.window.js +++ /dev/null @@ -1,42 +0,0 @@ -// META: timeout=long -// META: script=/common/utils.js -// META: script=beacon-common.sub.js - -"use strict"; - -// Execute each sample test with a cross-origin URL. If allowCors is 'true' -// the beacon handler will return CORS headers. This test ensures that the -// sendBeacon() succeeds in either case. -[true, false].forEach(function(allowCors) { - function buildUrl(id) { - const baseUrl = "http://{{domains[www]}}:{{ports[http][0]}}"; - // Note that 'allowCors=true' is not necessary for the sendBeacon() to reach - // the server. Beacons use the HTTP POST method, which is a CORS-safelisted - // method, and thus they do not trigger preflight. If the server does not - // respond with Access-Control-Allow-Origin and Access-Control-Allow-Credentials - // headers, an error will be printed to the console, but the request will - // already have reached the server. Since beacons are fire-and-forget, the - // error will not affect any client script, either -- not even the return - // value of the sendBeacon() call, because the underlying fetch is asynchronous. - // The "Beacon CORS" tests are merely testing that sendBeacon() to a cross- - // origin URL *will* work regardless. - const additionalQuery = allowCors ? "&origin=http://{{host}}:{{ports[http][0]}}&credentials=true" : ""; - return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}${additionalQuery}` - } - runTests(sampleTests, allowCors ? "-CORS-ALLOW" : "-CORS-FORBID", buildUrl); -}); - -// Now test a cross-origin request that doesn't use a safelisted Content-Type and ensure -// we are applying the proper restrictions. Since a non-safelisted Content-Type request -// header is used there should be a preflight/options request and we should only succeed -// send the payload if the proper CORS headers are used. -{ - function buildUrl(id) { - const baseUrl = "http://{{domains[www]}}:{{ports[http][0]}}"; - const additionalQuery = "&origin=http://{{host}}:{{ports[http][0]}}&credentials=true&preflightExpected=true"; - return `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}${additionalQuery}` - } - runTests(preflightTests, "-PREFLIGHT-ALLOW", buildUrl); -} - -done(); diff --git a/tests/wpt/web-platform-tests/beacon/beacon-error.sub.window.js b/tests/wpt/web-platform-tests/beacon/beacon-error.sub.window.js deleted file mode 100644 index 499fa3b2721..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-error.sub.window.js +++ /dev/null @@ -1,48 +0,0 @@ -// META: script=/common/utils.js -// META: script=beacon-common.sub.js - -"use strict"; - -test(function() { - // Payload that should cause sendBeacon to return false because it exceeds the maximum payload size. - var exceedPayload = Array(maxPayloadSize + 1).fill('z').join(""); - - var success = navigator.sendBeacon("http://{{hosts[][nonexistent]}}", exceedPayload); - assert_false(success, "calling 'navigator.sendBeacon()' with payload size exceeding the maximum size must fail"); -}, "Verify calling 'navigator.sendBeacon()' with a large payload returns 'false'."); - -test(function() { - var invalidUrl = "http://invalid:url"; - assert_throws_js(TypeError, function() { navigator.sendBeacon(invalidUrl, smallPayload); }, - `calling 'navigator.sendBeacon()' with an invalid URL '${invalidUrl}' must throw a TypeError`); -}, "Verify calling 'navigator.sendBeacon()' with an invalid URL throws an exception."); - -test(function() { - var invalidUrl = "nothttp://invalid.url"; - assert_throws_js(TypeError, function() { navigator.sendBeacon(invalidUrl, smallPayload); }, - `calling 'navigator.sendBeacon()' with a non-http(s) URL '${invalidUrl}' must throw a TypeError`); -}, "Verify calling 'navigator.sendBeacon()' with a URL that is not a http(s) scheme throws an exception."); - -// We'll validate that we can send one beacon that uses our entire Quota and then fail to send one that is just one char. -promise_test(async () => { - function wait(ms) { - return new Promise(res => step_timeout(res, ms)); - } - const url = '/fetch/api/resources/trickle.py?count=1&ms=0'; - assert_true(navigator.sendBeacon(url, maxPayload), - "calling 'navigator.sendBeacon()' with our max payload size should succeed."); - - // Now we'll send just one character. - assert_false(navigator.sendBeacon(url, '1'), - "calling 'navigator.sendBeacon()' with just one char should fail while our Quota is used up."); - - for (let i = 0; i < 20; ++i) { - await wait(100); - if (navigator.sendBeacon(url, maxPayload)) { - return; - } - } - assert_unreached('The quota should recover after fetching.'); -}, "Verify the behavior after the quota is exhausted."); - -done(); diff --git a/tests/wpt/web-platform-tests/beacon/beacon-navigate.html b/tests/wpt/web-platform-tests/beacon/beacon-navigate.html deleted file mode 100644 index 5df13905978..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-navigate.html +++ /dev/null @@ -1,19 +0,0 @@ -<!DOCTYPE html> -<html> -<head> - <title>W3C Beacon Outliving Navigation Test</title> - <meta name="timeout" content="long"> - <meta name="author" title="Microsoft Edge" href="https://www.microsoft.com"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> -<body> - <script src="/common/utils.js"></script> - <script src="beacon-common.sub.js"></script> - <script> - "use strict"; - - runSendInIframeAndNavigateTests("beacon"); - </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/beacon/beacon-navigate.https.window.js b/tests/wpt/web-platform-tests/beacon/beacon-navigate.https.window.js new file mode 100644 index 00000000000..8b42a47cd98 --- /dev/null +++ b/tests/wpt/web-platform-tests/beacon/beacon-navigate.https.window.js @@ -0,0 +1,23 @@ +// META: timeout=long +// META: script=/common/utils.js +// META: script=/common/get-host-info.sub.js +// META: script=beacon-common.sub.js + +'use strict'; + +const {HTTP_REMOTE_ORIGIN} = get_host_info(); + +for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) { + parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(SMALL, type); + const id = token(); + const url = `/beacon/resources/beacon.py?cmd=store&id=${id}`; + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + + iframe.src = `${HTTP_REMOTE_ORIGIN}/common/blank.html`; + }, `The frame navigates away after calling sendBeacon[type = ${type}].`); +} diff --git a/tests/wpt/web-platform-tests/beacon/beacon-preflight-failure.sub.window.js b/tests/wpt/web-platform-tests/beacon/beacon-preflight-failure.sub.window.js deleted file mode 100644 index c5a2d813f17..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-preflight-failure.sub.window.js +++ /dev/null @@ -1,28 +0,0 @@ -// META: script=/common/utils.js -// META: script=/common/get-host-info.sub.js - -promise_test(async (test) => { - const origin = get_host_info().REMOTE_ORIGIN; - const id = token(); - const store = `${origin}/beacon/resources/beacon.py?cmd=store&id=${id}`; - const monitor = `/beacon/resources/beacon.py?cmd=stat&id=${id}`; - - assert_true(navigator.sendBeacon(store, new Blob([], {type: 'x/y'}))); - - let actual; - for (let i = 0; i < 30; ++i) { - await new Promise(resolve => test.step_timeout(resolve, 10)); - - const response = await fetch(monitor); - const obj = await response.json(); - if (obj.length > 0) { - actual = JSON.stringify(obj); - break; - } - } - - const expected = - JSON.stringify([{error: 'Preflight not expected.'}]); - - assert_equals(actual, expected); -}); diff --git a/tests/wpt/web-platform-tests/beacon/beacon-readablestream.window.js b/tests/wpt/web-platform-tests/beacon/beacon-readablestream.window.js deleted file mode 100644 index 46e30fcda5c..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-readablestream.window.js +++ /dev/null @@ -1,3 +0,0 @@ -test(() => { - assert_throws_js(TypeError, () => navigator.sendBeacon("...", new ReadableStream())); -}, "sendBeacon() with a stream does not work due to the keepalive flag being set"); diff --git a/tests/wpt/web-platform-tests/beacon/beacon-redirect.https.window.js b/tests/wpt/web-platform-tests/beacon/beacon-redirect.https.window.js new file mode 100644 index 00000000000..16a2545527e --- /dev/null +++ b/tests/wpt/web-platform-tests/beacon/beacon-redirect.https.window.js @@ -0,0 +1,35 @@ +// META: timeout=long +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=beacon-common.sub.js + +'use strict'; + +const {ORIGIN} = get_host_info(); + +// Execute each sample test per redirect status code. +// Note that status codes 307 and 308 are the only codes that will maintain POST +// data through a redirect. +for (const status of [307, 308]) { + for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) { + parallelPromiseTest(async (t) => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + t.add_cleanup(() => iframe.remove()); + + const payload = makePayload(SMALL, type); + const id = token(); + const destination = + `${ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`; + const url = `${ORIGIN}/common/redirect.py` + + `?status=${status}&location=${encodeURIComponent(destination)}`; + + assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload)); + iframe.remove(); + + await waitForResult(id); + }, `cross-origin, CORS-safelisted: status = ${status}, type = ${type}`); + } +}; + +done(); diff --git a/tests/wpt/web-platform-tests/beacon/beacon-redirect.sub.window.js b/tests/wpt/web-platform-tests/beacon/beacon-redirect.sub.window.js deleted file mode 100644 index fd23a45f86b..00000000000 --- a/tests/wpt/web-platform-tests/beacon/beacon-redirect.sub.window.js +++ /dev/null @@ -1,20 +0,0 @@ -// META: timeout=long -// META: script=/common/utils.js -// META: script=beacon-common.sub.js - -"use strict"; - -// Execute each sample test per redirect status code. -// Note that status codes 307 and 308 are the only codes that will maintain POST data -// through a redirect. -[307, 308].forEach(function(status) { - function buildUrl(id) { - const baseUrl = "http://{{host}}:{{ports[http][0]}}"; - const targetUrl = `${baseUrl}/beacon/resources/beacon.py?cmd=store&id=${id}`; - - return `/common/redirect.py?status=${status}&location=${encodeURIComponent(targetUrl)}`; - } - runTests(sampleTests, `-${status}`, buildUrl); -}); - -done(); diff --git a/tests/wpt/web-platform-tests/beacon/resources/beacon.py b/tests/wpt/web-platform-tests/beacon/resources/beacon.py index f5caaf4b8d3..d81bfb1ac68 100644 --- a/tests/wpt/web-platform-tests/beacon/resources/beacon.py +++ b/tests/wpt/web-platform-tests/beacon/resources/beacon.py @@ -24,8 +24,13 @@ def main(request, response): nature of the stash, results for a given test are only guaranteed to be returned once, though they may be returned multiple times. + An entry may contain following members. + - error: An error string. null if there is no error. + - type: The content-type header of the request "(missing)" if there + is no content-type header in the request. + Example response bodies: - - [{error: null}] + - [{error: null, type: "text/plain;charset=UTF8"}] - [{error: "some validation details"}] - [] @@ -53,8 +58,9 @@ def main(request, response): # requests we may get. if request.method == u"POST": payload = b"" - if b"Content-Type" in request.headers and \ - b"form-data" in request.headers[b"Content-Type"]: + contentType = request.headers[b"Content-Type"] \ + if b"Content-Type" in request.headers else b"(missing)" + if b"form-data" in contentType: if b"payload" in request.POST: # The payload was sent as a FormData. payload = request.POST.first(b"payload") @@ -71,20 +77,26 @@ def main(request, response): # Confirm the payload size sent matches with the number of # characters sent. - if payload_size != len(payload_parts[1]): + if payload_size != len(payload): error = u"expected %d characters but got %d" % ( - payload_size, len(payload_parts[1])) + payload_size, len(payload)) else: # Confirm the payload contains the correct characters. - for i in range(0, payload_size): - if payload_parts[1][i:i+1] != b"*": + for i in range(len(payload)): + if i <= len(payload_parts[0]): + continue + c = payload[i:i+1] + if c != b"*": error = u"expected '*' at index %d but got '%s''" % ( - i, isomorphic_decode(payload_parts[1][i:i+1])) + i, isomorphic_decode(c)) break # Store the result in the stash so that it can be retrieved # later with a 'stat' command. - request.server.stash.put(id, {u"error": error}) + request.server.stash.put(id, { + u"error": error, + u"type": isomorphic_decode(contentType) + }) elif request.method == u"OPTIONS": # If we expect a preflight, then add the cors headers we expect, # otherwise log an error as we shouldn't send a preflight for all diff --git a/tests/wpt/web-platform-tests/clipboard-apis/clipboard-file-manual.html b/tests/wpt/web-platform-tests/clipboard-apis/clipboard-file-manual.html new file mode 100644 index 00000000000..e934f2fd0d1 --- /dev/null +++ b/tests/wpt/web-platform-tests/clipboard-apis/clipboard-file-manual.html @@ -0,0 +1,87 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Clipboard: DataTransfer File manual test</title> +<link rel="help" href="https://w3c.github.io/clipboard-apis/#to-fire-a-clipboard-event"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> +#pastewrapper { + display: block; + width: 400px; + height: 200px; + position: relative; + padding: 50px 0 0 100px; +} +#pastezone { + display: block; + border: 1px solid black; + width: 200px; + height: 100px; +} +</style> +<p> + Please download <a download href="resources/copied-file.txt">this file</a>, + and copy and paste it into the box below. +</p> + +<div id="pastewrapper"> + <div id="pastezone"> + Paste Here + </div> +</div> + +<script> +'use strict'; + +const pasteWrapper = document.querySelector('#pastewrapper'); +const pasteZone = document.querySelector('#pastezone'); + +const pastePromise = new Promise((resolve, reject) => { + pasteZone.onpaste = event => { + event.preventDefault(); + + // Copy the information out of the DataTransfer instance before it is + // neutered when the event handler exits. + const dataTransfer = event.clipboardData; + const items = Array.from(dataTransfer.items).map(item => { + return {kind: item.kind, type: item.type, file: item.getAsFile() }; + }); + resolve({ types: dataTransfer.types, files: dataTransfer.files, items }); + }; +}); + +promise_test(async () => { + const dataTransfer = await pastePromise; + assert_true(dataTransfer.types.includes('Files')); +}, 'DataTransfer.types in paste file'); + +promise_test(async () => { + const dataTransfer = await pastePromise; + assert_equals( + dataTransfer.files.length, 1, + 'DataTransfer.files should have one element'); + const file = dataTransfer.files[0]; + assert_true( + file instanceof File, + 'DataTransfer.files[0] should be a File instance'); + assert_equals(file.name, 'copied-file.txt'); + assert_equals(file.type, 'text/plain'); + assert_equals(file.size, 21); + assert_equals(await file.text(), 'copied-file-contents\n'); +}, 'DataTransfer.files in paste'); + +promise_test(async () => { + const dataTransfer = await pastePromise; + const items = dataTransfer.items.filter(i => i.kind === 'file'); + assert_equals(items.length, 1, + 'DataTransfer.items[kind="file"] should have 1 element'); + const item = items[0]; + assert_true( + item.file instanceof File, + 'DataTransfer.items[0] should be a File instance'); + assert_equals(item.file.name, 'copied-file.txt'); + assert_equals(item.file.type, 'text/plain'); + assert_equals(item.file.size, 21); + assert_equals(await item.file.text(), 'copied-file-contents\n'); +}, 'DataTransfer.items in paste'); +</script> diff --git a/tests/wpt/web-platform-tests/clipboard-apis/resources/copied-file.txt b/tests/wpt/web-platform-tests/clipboard-apis/resources/copied-file.txt new file mode 100644 index 00000000000..56a2838b7d6 --- /dev/null +++ b/tests/wpt/web-platform-tests/clipboard-apis/resources/copied-file.txt @@ -0,0 +1 @@ +copied-file-contents diff --git a/tests/wpt/web-platform-tests/content-security-policy/README.html b/tests/wpt/web-platform-tests/content-security-policy/README.html index 8986d736a0e..07ddcc7a4d8 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/README.html +++ b/tests/wpt/web-platform-tests/content-security-policy/README.html @@ -71,7 +71,7 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: <span class=highlight2>script-src-1_1</span>={{$id:uuid()}}; Path=<span class=highlight2>/content-security-policy/script-src/</span> -Content-Security-Policy: <span class=highlight1>script-src 'self'</span>; report-uri <span class=highlight2>..</span>/support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: <span class=highlight1>script-src 'self'</span>; report-uri <span class=highlight2></span>/reporting/resources/report.py?op=put&reportID={{$id}} </code></pre> <p>This sets some headers to prevent caching (just so we are more likely to see our latest changes if we're actively developing this test) sets a cookie (more on that later) and sets the relevant <span class=code>Content-Security-Policy</span> header for our test case.</p> diff --git a/tests/wpt/web-platform-tests/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html.sub.headers index 1e3f163730f..811125d83ae 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/base-uri/report-uri-does-not-respect-base-uri.sub.html.sub.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: report-uri-does-not-respect-base-uri={{$id:uuid()}}; Path=/content-security-policy/base-uri -Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required-csp-header-cascade.html b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required-csp-header-cascade.html index f130b1714e7..92fe2dd4310 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required-csp-header-cascade.html +++ b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required-csp-header-cascade.html @@ -34,8 +34,8 @@ "csp2": "script-src 'unsafe-inline'; style-src 'self';", "expected1": null, "expected2": "script-src 'unsafe-inline'; style-src 'self';"}, - { "name": "Test invalid policy on first iframe (bad directive)", - "csp1": "default-src http://example.com; invalid-policy-name http://example.com", + { "name": "Test invalid policy on first iframe (bad directive name)", + "csp1": "default-src http://example.com; i//nvalid-policy-name http://example.com", "csp2": "script-src 'unsafe-inline'; style-src 'self';", "expected1": null, "expected2": "script-src 'unsafe-inline'; style-src 'self';"}, @@ -44,9 +44,9 @@ "csp2": "script-src 'unsafe-inline'; style-src 'self';", "expected1": null, "expected2": "script-src 'unsafe-inline'; style-src 'self';"}, - { "name": "Test invalid policy on second iframe (bad directive)", + { "name": "Test invalid policy on second iframe (bad directive name)", "csp1": "script-src 'unsafe-inline'; style-src 'self';", - "csp2": "default-src http://example.com; invalid-policy-name http://example.com", + "csp2": "default-src http://example.com; i//nvalid-policy-name http://example.com", "expected1": "script-src 'unsafe-inline'; style-src 'self';", "expected2": "script-src 'unsafe-inline'; style-src 'self';"}, { "name": "Test invalid policy on second iframe (report directive)", diff --git a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header-crlf.html b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header-crlf.html index ae121899023..414f9b73f5a 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header-crlf.html +++ b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header-crlf.html @@ -16,126 +16,64 @@ var tests = [ // CRLF characters { "name": "\\r\\n character after directive name", - "csp": "script-src\r\n'unsafe-inline'", + "csp": "style-src\r\n'unsafe-inline'", "expected": null }, { "name": "\\r\\n character in directive value", - "csp": "script-src 'unsafe-inline'\r\n'unsafe-eval'", + "csp": "style-src 'unsafe-inline'\r\n'unsafe-eval'", "expected": null }, { "name": "\\n character after directive name", - "csp": "script-src\n'unsafe-inline'", + "csp": "style-src\n'unsafe-inline'", "expected": null }, { "name": "\\n character in directive value", - "csp": "script-src 'unsafe-inline'\n'unsafe-eval'", + "csp": "style-src 'unsafe-inline'\n'unsafe-eval'", "expected": null }, { "name": "\\r character after directive name", - "csp": "script-src\r'unsafe-inline'", + "csp": "style-src\r'unsafe-inline'", "expected": null }, { "name": "\\r character in directive value", - "csp": "script-src 'unsafe-inline'\r'unsafe-eval'", - "expected": null }, - - // HTML encoded CRLF characters - { "name": "%0D%0A character after directive name", - "csp": "script-src%0D%0A'unsafe-inline'", - "expected": null }, - { "name": "%0D%0A character in directive value", - "csp": "script-src 'unsafe-inline'%0D%0A'unsafe-eval'", - "expected": null }, - { "name": "%0A character after directive name", - "csp": "script-src%0A'unsafe-inline'", - "expected": null }, - { "name": "%0A character in directive value", - "csp": "script-src 'unsafe-inline'%0A'unsafe-eval'", - "expected": null }, - { "name": "%0D character after directive name", - "csp": "script-src%0D'unsafe-inline'", - "expected": null }, - { "name": "%0D character in directive value", - "csp": "script-src 'unsafe-inline'%0D'unsafe-eval'", + "csp": "style-src 'unsafe-inline'\r'unsafe-eval'", "expected": null }, // Attempt HTTP Header injection { "name": "Attempt injecting after directive name using \\r\\n", - "csp": "script-src\r\nTest-Header-Injection: dummy", + "csp": "style-src\r\nTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after directive name using \\r", - "csp": "script-src\rTest-Header-Injection: dummy", + "csp": "style-src\rTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after directive name using \\n", - "csp": "script-src\nTest-Header-Injection: dummy", + "csp": "style-src\nTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after directive value using \\r\\n", - "csp": "script-src example.com\r\nTest-Header-Injection: dummy", + "csp": "style-src example.com\r\nTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after directive value using \\r", - "csp": "script-src example.com\rTest-Header-Injection: dummy", + "csp": "style-src example.com\rTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after directive value using \\n", - "csp": "script-src example.com\nTest-Header-Injection: dummy", + "csp": "style-src example.com\nTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after semicolon using \\r\\n", - "csp": "script-src example.com;\r\nTest-Header-Injection: dummy", + "csp": "style-src example.com;\r\nTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after semicolon using \\r", - "csp": "script-src example.com;\rTest-Header-Injection: dummy", + "csp": "style-src example.com;\rTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after semicolon using \\n", - "csp": "script-src example.com;\nTest-Header-Injection: dummy", + "csp": "style-src example.com;\nTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after space between name and value using \\r\\n", - "csp": "script-src \r\nTest-Header-Injection: dummy", + "csp": "style-src \r\nTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after space between name and value using \\r", - "csp": "script-src \rTest-Header-Injection: dummy", + "csp": "style-src \rTest-Header-Injection: dummy", "expected": null }, { "name": "Attempt injecting after space between name and value using \\n", - "csp": "script-src \nTest-Header-Injection: dummy", - "expected": null }, - - // Attempt HTTP Header injection using URL encoded characters - { "name": "Attempt injecting after directive name using %0D%0A", - "csp": "script-src%0D%0ATest-Header-Injection: dummy", - "expected": null }, - { "name": "Attempt injecting after directive name using %0D", - "csp": "script-src%0DTest-Header-Injection: dummy", - "expected": null }, - { "name": "Attempt injecting after directive name using %0A", - "csp": "script-src%0ATest-Header-Injection: dummy", - "expected": null }, - - { "name": "Attempt injecting after directive value using %0D%0A", - "csp": "script-src example.com%0D%0ATest-Header-Injection: dummy", + "csp": "style-src \nTest-Header-Injection: dummy", "expected": null }, - { "name": "Attempt injecting after directive value using %0D", - "csp": "script-src example.com%0DTest-Header-Injection: dummy", - "expected": null }, - { "name": "Attempt injecting after directive value using %0A", - "csp": "script-src example.com%0ATest-Header-Injection: dummy", - "expected": null }, - - { "name": "Attempt injecting after semicolon using %0D%0A", - "csp": "script-src example.com;%0D%0ATest-Header-Injection: dummy", - "expected": null }, - { "name": "Attempt injecting after semicolon using %0D", - "csp": "script-src example.com;%0DTest-Header-Injection: dummy", - "expected": null }, - { "name": "Attempt injecting after semicolon using %0A", - "csp": "script-src example.com;%0ATest-Header-Injection: dummy", - "expected": null }, - - { "name": "Attempt injecting after space between name and value using %0D%0A", - "csp": "script-src %0D%0ATest-Header-Injection: dummy", - "expected": null }, - { "name": "Attempt injecting after space between name and value using %0D", - "csp": "script-src %0DTest-Header-Injection: dummy", - "expected": null }, - { "name": "Attempt injecting after space between name and value using %0A", - "csp": "script-src %0ATest-Header-Injection: dummy", - "expected": null }, - ]; tests.forEach(test => { diff --git a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header.html b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header.html index fbaa42d56ee..a9ad7874087 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header.html +++ b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/required_csp-header.html @@ -24,39 +24,39 @@ { "name": "Send Sec-Required-CSP Header on change of `src` attribute on iframe.", "csp": "script-src 'unsafe-inline'", "expected": "script-src 'unsafe-inline'" }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - gibberish csp", + { "name": "Wrong but allowed value of `csp` should still trigger sending Sec-Required-CSP Header - gibberish csp", "csp": "completely wrong csp", - "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - unknown policy name", + "expected": "completely wrong csp" }, + { "name": "Wrong but allowed value of `csp` should still trigger sending Sec-Required-CSP Header - unknown policy name", "csp": "invalid-policy-name http://example.com", + "expected": "invalid-policy-name http://example.com" }, + { "name": "Wrong but allowed value of `csp` should still trigger sending Sec-Required-CSP Header - unknown policy name in multiple directives", + "csp": "media-src http://example.com; invalid-policy-name http://example.com", + "expected": "media-src http://example.com; invalid-policy-name http://example.com" }, + { "name": "Wrong but allowed value of `csp` should still trigger sending Sec-Required-CSP Header - misspeled 'none'", + "csp": "media-src 'non'", + "expected": "media-src 'non'" }, + { "name": "Wrong but allowed value of `csp` should still trigger sending Sec-Required-CSP Header - query values in path", + "csp": "script-src 'unsafe-inline' 127.0.0.1:8000/path?query=string", + "expected": "script-src 'unsafe-inline' 127.0.0.1:8000/path?query=string" }, + { "name": "Wrong but allowed value of `csp` should still trigger sending Sec-Required-CSP Header - missing semicolon", + "csp": "script-src 'unsafe-inline' 'self' object-src 'self' style-src *", + "expected": "script-src 'unsafe-inline' 'self' object-src 'self' style-src *" }, + { "name": "Wrong and dangerous value of `csp` should not trigger sending Sec-Required-CSP Header - comma separated", + "csp": "script-src 'unsafe-inline' 'self', object-src 'none'", "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - unknown policy name in multiple directives", - "csp": "default-src http://example.com; invalid-policy-name http://example.com", - "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - misspeled 'none'", - "csp": "default-src 'non'", - "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - query values in path", - "csp": "script-src 127.0.0.1:8000/path?query=string", - "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - missing semicolon", - "csp": "script-src 'self' object-src 'self' style-src *", - "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - comma separated", - "csp": "script-src 'none', object-src 'none'", - "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - html encoded string", + { "name": "Wrong and dangerous value of `csp` should not trigger sending Sec-Required-CSP Header - invalid characters in directive names", // script-src 127.0.0.1:8000 - "csp": "script-src 127.0.0.1:8000", + "csp": "script-src 'unsafe-inline' 127.0.0.1:8000", "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - url encoded string", + { "name": "Wrong and dangerous value of `csp` should not trigger sending Sec-Required-CSP Header - invalid character in directive name", // script-src 127.0.0.1:8000 - "csp": "script-src%20127.0.0.1%3A8000", + "csp": "media-src%20127.0.0.1%3A8000", "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - report-uri present", + { "name": "Wrong and dangerous value of `csp` should not trigger sending Sec-Required-CSP Header - report-uri present", "csp": "script-src 'unsafe-inline'; report-uri resources/dummy-report.php", "expected": null }, - { "name": "Wrong value of `csp` should not trigger sending Sec-Required-CSP Header - report-to present", + { "name": "Wrong and dangerous value of `csp` should not trigger sending Sec-Required-CSP Header - report-to present", "csp": "script-src 'unsafe-inline'; report-to resources/dummy-report.php", "expected": null }, ]; diff --git a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/subsumption_algorithm-general.html b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/subsumption_algorithm-general.html index a275096ce2e..14dbf5211f2 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/subsumption_algorithm-general.html +++ b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/subsumption_algorithm-general.html @@ -69,14 +69,18 @@ "returned_csp": "style-src http://*.example.com:*", "returned_csp_2": "style-src http://*.com", "expected": IframeLoad.EXPECT_LOAD }, - { "name": "Iframe should block if plugin-types directive is not subsumed.", + { "name": "Removed plugin-types directive should be ignored.", "required_csp": "plugin-types application/pdf", "returned_csp": null, - "expected": IframeLoad.EXPECT_BLOCK }, - { "name": "Iframe should load if plugin-types directive is subsumed.", + "expected": IframeLoad.EXPECT_LOAD }, + { "name": "Removed plugin-types directive should be ignored 2.", "required_csp": "plugin-types application/pdf application/x-java-applet", "returned_csp": "plugin-types application/pdf", "expected": IframeLoad.EXPECT_LOAD }, + { "name": "Removed plugin-types directive should be ignored 3.", + "required_csp": "style-src 'none'; plugin-types application/pdf", + "returned_csp": null, + "expected": IframeLoad.EXPECT_BLOCK }, ]; tests.forEach(test => { diff --git a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/support/echo-required-csp.py b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/support/echo-required-csp.py index 90fc1d11763..b704dfe92f5 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/support/echo-required-csp.py +++ b/tests/wpt/web-platform-tests/content-security-policy/embedded-enforcement/support/echo-required-csp.py @@ -11,9 +11,6 @@ def main(request, response): header = request.headers.get(b"Sec-Required-CSP"); message[u'required_csp'] = isomorphic_decode(header) if header else None - header = request.headers.get(b"Sec-Required-CSP"); - message[u'required_csp'] = isomorphic_decode(header) if header else None - second_level_iframe_code = u"" if b"include_second_level_iframe" in request.GET: if b"second_level_iframe_csp" in request.GET and request.GET[b"second_level_iframe_csp"] != b"": diff --git a/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-blocked-frame.sub.html b/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-blocked-frame.sub.html index 69c098d5597..a7532b7cf2f 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-blocked-frame.sub.html +++ b/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-blocked-frame.sub.html @@ -7,7 +7,7 @@ <title>Blocked frames are reported correctly</title> </head> <body> - <iframe src="support/content-security-policy.sub.html?policy=report-uri%20../../support/report.py%3Fop=put%26reportID={{$id:uuid()}}%3B%20frame-ancestors%20'none'"></iframe> + <iframe src="support/content-security-policy.sub.html?policy=report-uri%20/reporting/resources/report.py%3Fop=put%26reportID={{$id:uuid()}}%3B%20frame-ancestors%20'none'"></iframe> <script async defer src="../support/checkReport.sub.js?reportField=violated-directive&reportValue=frame-ancestors%20'none'&reportID={{$id}}"></script> </body> </html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-only-frame.sub.html b/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-only-frame.sub.html index 28dd4bebeca..55289db6d60 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-only-frame.sub.html +++ b/tests/wpt/web-platform-tests/content-security-policy/frame-ancestors/report-only-frame.sub.html @@ -7,7 +7,7 @@ <title>Blocked frames are reported correctly</title> </head> <body> - <iframe src="support/content-security-policy-report-only.sub.html?policy=report-uri%20../../support/report.py%3Fop=put%26reportID={{$id:uuid()}}%3B%20frame-ancestors%20'none'"></iframe> + <iframe src="support/content-security-policy-report-only.sub.html?policy=report-uri%20/reporting/resources/report.py%3Fop=put%26reportID={{$id:uuid()}}%3B%20frame-ancestors%20'none'"></iframe> <script async defer src="../support/checkReport.sub.js?reportField=violated-directive&reportValue=frame-ancestors%20'none'&reportID={{$id}}"></script> </body> </html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/generic/no-default-src.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/generic/no-default-src.sub.html.sub.headers index a7337acceb9..b40d6ffbab6 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/generic/no-default-src.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/generic/no-default-src.sub.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: no-default-src={{$id:uuid()}}; Path=/content-security-policy/generic/ -Content-Security-Policy: foobar; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: foobar; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/generic/policy-inherited-correctly-by-plznavigate.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/generic/policy-inherited-correctly-by-plznavigate.html.sub.headers index 64653e3bf1a..73fb991fb1f 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/generic/policy-inherited-correctly-by-plznavigate.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/generic/policy-inherited-correctly-by-plznavigate.html.sub.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: policy-inherited-correctly-by-plznavigate={{$id:uuid()}}; Path=/content-security-policy/generic/ -Content-Security-Policy: frame-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: frame-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-blob-scheme.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-blob-scheme.html.sub.headers index cd80b326ff7..adc398d890f 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-blob-scheme.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-blob-scheme.html.sub.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: sandboxed-blob-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ -Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-data-scheme.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-data-scheme.html.sub.headers index 766d3e0e050..96da6514b85 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-data-scheme.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/inheritance/sandboxed-data-scheme.html.sub.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: sandboxed-data-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ -Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/inheritance/support/navigate-self-to-blob.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/inheritance/support/navigate-self-to-blob.html.sub.headers index 27aa5f4a102..2642b0fa067 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/inheritance/support/navigate-self-to-blob.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/inheritance/support/navigate-self-to-blob.html.sub.headers @@ -1,4 +1,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache -Content-Security-Policy: {{GET[csp]}}; report-uri http://{{host}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}} +Content-Security-Policy: {{GET[csp]}}; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html.sub.headers index 4cf3e34ce97..b1054d35064 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html.sub.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: unsandboxed-blob-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ -Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-data-scheme.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-data-scheme.html.sub.headers index 9cfb8aaa819..f4a6088578f 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-data-scheme.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/inheritance/unsandboxed-data-scheme.html.sub.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: unsandboxed-data-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ -Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/parent-navigates-child-blocked.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/parent-navigates-child-blocked.html.sub.headers index 6784a56c8eb..36238fa78a6 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/parent-navigates-child-blocked.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/parent-navigates-child-blocked.html.sub.headers @@ -2,4 +2,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: parent-navigates-child-blocked={{$id:uuid()}}; Path=/content-security-policy/navigate-to/ -Content-Security-Policy: navigate-to support/wait_for_navigation.html; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: navigate-to support/wait_for_navigation.html; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/form_action_navigation.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/form_action_navigation.sub.html.sub.headers index 9c572a96162..a42cfe2d958 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/form_action_navigation.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/form_action_navigation.sub.html.sub.headers @@ -1,4 +1,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache -Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}}
\ No newline at end of file +Content-Security-Policy: {{GET[csp]}}; report-uri /reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/href_location_navigation.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/href_location_navigation.sub.html.sub.headers index d01e2672a83..a42cfe2d958 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/href_location_navigation.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/href_location_navigation.sub.html.sub.headers @@ -1,4 +1,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache -Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}} +Content-Security-Policy: {{GET[csp]}}; report-uri /reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/link_click_navigation.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/link_click_navigation.sub.html.sub.headers index d01e2672a83..a42cfe2d958 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/link_click_navigation.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/link_click_navigation.sub.html.sub.headers @@ -1,4 +1,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache -Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}} +Content-Security-Policy: {{GET[csp]}}; report-uri /reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html.sub.headers index d01e2672a83..a42cfe2d958 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/meta_refresh_navigation.sub.html.sub.headers @@ -1,4 +1,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache -Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}} +Content-Security-Policy: {{GET[csp]}}; report-uri /reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/navigate_parent.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/navigate_parent.sub.html.sub.headers index d01e2672a83..a42cfe2d958 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/navigate_parent.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/navigate_parent.sub.html.sub.headers @@ -1,4 +1,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache -Content-Security-Policy: {{GET[csp]}}; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}} +Content-Security-Policy: {{GET[csp]}}; report-uri /reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/spv-test-iframe1.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/spv-test-iframe1.sub.html.sub.headers index 50d77dc7dbb..9d83b92d96c 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/spv-test-iframe1.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigate-to/support/spv-test-iframe1.sub.html.sub.headers @@ -1,4 +1,4 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache -Content-Security-Policy: navigate-to {{location[server]}}/content-security-policy/navigate-to/support/spv-test-iframe3.sub.html 'unsafe-allow-redirects'; report-uri /content-security-policy/support/report.py?op=put&reportID={{GET[report_id]}} +Content-Security-Policy: navigate-to {{location[server]}}/content-security-policy/navigate-to/support/spv-test-iframe3.sub.html 'unsafe-allow-redirects'; report-uri /reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/navigation/support/test_csp_self_window.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/navigation/support/test_csp_self_window.sub.html.sub.headers index dd418ec7648..5024a99bc97 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/navigation/support/test_csp_self_window.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/navigation/support/test_csp_self_window.sub.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: {{GET[report_cookie_name]}}={{$id:uuid()}}; Path=/content-security-policy/navigation/ -Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html index 65c575c4b54..faa963cb355 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html @@ -4,7 +4,7 @@ <head> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> - <!-- Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} --> + <!-- Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html.sub.headers index 012702bfc1a..071eb96fc0a 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-no-url-allowed.html.sub.headers @@ -1,2 +1,2 @@ Set-Cookie: object-src-no-url-allowed={{$id:uuid()}}; Path=/content-security-policy/object-src/ -Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html index 89d431c0b50..07c53ceb1b8 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html @@ -8,7 +8,7 @@ Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; - report-uri ../support/report.py?op=put&reportID={{$id}} + report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html.sub.headers index 9372a723c87..58ddd21445e 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-allowed.html.sub.headers @@ -1,2 +1,2 @@ Set-Cookie: object-src-url-allowed={{$id:uuid()}}; Path=/content-security-policy/object-src/ -Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html index cc64dff27a7..a7cdbc9e9f3 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html @@ -8,7 +8,7 @@ Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; - report-uri ../support/report.py?op=put&reportID={{$id}} + report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html.sub.headers index 7c20bf3d400..29a3987e304 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-embed-allowed.html.sub.headers @@ -1,2 +1,2 @@ Set-Cookie: object-src-url-embed-allowed={{$id:uuid()}}; Path=/content-security-policy/object-src/ -Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html index c52286fc129..18d796b0e99 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html @@ -4,7 +4,7 @@ <head> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> - <!-- Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} --> + <!-- Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html.sub.headers index 82779ec642a..10b5543c023 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/object-src/object-src-url-redirect-allowed.html.sub.headers @@ -1,2 +1,2 @@ Set-Cookie: object-src-url-redirect-allowed={{$id:uuid()}}; Path=/content-security-policy/object-src/ -Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: object-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugin-types-ignored.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugin-types-ignored.html new file mode 100644 index 00000000000..cf27cdfc54b --- /dev/null +++ b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugin-types-ignored.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> + +<body> + <!-- Test that the old CSP directive 'plugin-types' has no effect anymore --> + <object type="application/pdf"></object> + <!-- we rely on the report because we can't rely on the onload event for + "allowed" tests as it is not fired for object and embed --> + <script async defer src='../support/checkReport.sub.js?reportExists=false'></script> +</body> + +</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugin-types-ignored.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugin-types-ignored.html.sub.headers new file mode 100644 index 00000000000..5508935fff4 --- /dev/null +++ b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugin-types-ignored.html.sub.headers @@ -0,0 +1,6 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Cache-Control: post-check=0, pre-check=0, false +Pragma: no-cache +Set-Cookie: plugin-types-ignored={{$id:uuid()}}; Path=/content-security-policy/plugin-types/ +Content-Security-Policy: plugin-types application/x-shockwave-flash; report-uri ../support/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-empty.sub.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-empty.sub.html deleted file mode 100644 index 0cd1a70a1dd..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-empty.sub.html +++ /dev/null @@ -1,22 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta http-equiv="Content-Security-Policy" content="plugin-types ;"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> - -<body> - <script> - var t = async_test('Should not load the object because plugin-types allows no plugins'); - window.addEventListener('securitypolicyviolation', t.step_func_done(function(e) { - assert_equals(e.violatedDirective, "plugin-types"); - assert_equals(e.blockedURI, ""); - })); - </script> - - <object type="application/x-shockwave-flash" data="/content-security-policy/support/media/flash.swf"></object> -</body> - -</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-mismatched-data.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-mismatched-data.html deleted file mode 100644 index 430a3a1eb9f..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-mismatched-data.html +++ /dev/null @@ -1,22 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta http-equiv="Content-Security-Policy" content="plugin-types application/pdf;"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> - -<body> - <script> - var t = async_test('Should not load the object because its declared type does not match its actual type'); - window.addEventListener('securitypolicyviolation', t.step_func_done(function(e) { - assert_equals(e.violatedDirective, "plugin-types"); - assert_equals(e.blockedURI, ""); - })); - </script> - - <object type="application/pdf" data="data:application/x-shockwave-flash,asdf"></object> -</body> - -</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-mismatched-url.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-mismatched-url.html deleted file mode 100644 index 306d08f79e1..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-mismatched-url.html +++ /dev/null @@ -1,22 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta http-equiv="Content-Security-Policy" content="plugin-types application/pdf;"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> - -<body> - <script> - var t = async_test('Should not load the object because its declared type does not match its actual type'); - window.addEventListener('securitypolicyviolation', t.step_func_done(function(e) { - assert_equals(e.violatedDirective, "plugin-types"); - assert_equals(e.blockedURI, ""); - })); - </script> - - <object type="application/pdf" data="/content-security-policy/support/media/flash.swf"></object> -</body> - -</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-notype-data.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-notype-data.html deleted file mode 100644 index e10d5335772..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-notype-data.html +++ /dev/null @@ -1,23 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta http-equiv="Content-Security-Policy" content="plugin-types application/pdf;"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> - -<body> - <script> - var t = async_test('Should not load the object because it does not have a declared type'); - window.addEventListener('securitypolicyviolation', t.step_func_done(function(e) { - assert_equals(e.violatedDirective, "plugin-types"); - assert_equals(e.blockedURI, ""); - })); - </script> - - <!-- Objects need to declare an explicit type --> - <object data="data:application/x-shockwave-flash,asdf"></object> -</body> - -</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-notype-url.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-notype-url.html deleted file mode 100644 index 73ff7366e01..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-notype-url.html +++ /dev/null @@ -1,23 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta http-equiv="Content-Security-Policy" content="plugin-types application/pdf;"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> - -<body> - <script> - var t = async_test('Should not load the object because it does not have a declared type'); - window.addEventListener('securitypolicyviolation', t.step_func_done(function(e) { - assert_equals(e.violatedDirective, "plugin-types"); - assert_equals(e.blockedURI, ""); - })); - </script> - - <!-- Objects need to declare an explicit type --> - <object data="/content-security-policy/support/media/flash.swf"></object> -</body> - -</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-allowed.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-allowed.html deleted file mode 100644 index cd50086761f..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-allowed.html +++ /dev/null @@ -1,16 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> - -<body> - <object type="application/x-shockwave-flash"></object> - <!-- we rely on the report because we can't rely on the onload event for - "allowed" tests as it is not fired for object and embed --> - <script async defer src='../support/checkReport.sub.js?reportExists=false'></script> -</body> - -</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-allowed.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-allowed.html.sub.headers deleted file mode 100644 index 95cc52be322..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-allowed.html.sub.headers +++ /dev/null @@ -1,2 +0,0 @@ -Set-Cookie: plugintypes-nourl-allowed={{$id:uuid()}}; Path=/content-security-policy/plugin-types/ -Content-Security-Policy: plugin-types application/x-shockwave-flash; report-uri ../support/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-blocked.html b/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-blocked.html deleted file mode 100644 index 02da1e0d1e6..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/plugin-types/plugintypes-nourl-blocked.html +++ /dev/null @@ -1,22 +0,0 @@ -<!DOCTYPE html> -<html> - -<head> - <meta http-equiv="Content-Security-Policy" content="plugin-types application/pdf;"> - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> -</head> - -<body> - <script> - var t = async_test('Should not load the object because it does not match plugin-types'); - window.addEventListener('securitypolicyviolation', t.step_func_done(function(e) { - assert_equals(e.violatedDirective, "plugin-types"); - assert_equals(e.blockedURI, ""); - })); - </script> - - <object type="application/x-shockwave-flash"></object> -</body> - -</html> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers index 054d332035a..aa9c76cdb47 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-doesnt-send-reports-without-violation.https.sub.html.sub.headers @@ -3,5 +3,5 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: reporting-api-doesnt-send-reports-without-violation={{$id:uuid()}}; Path=/content-security-policy/reporting-api -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] } +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}" }] } Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'self'; report-to csp-group diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers index 973c20e23f8..10addf82bcc 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-only-sends-reports-on-violation.https.sub.html.sub.headers @@ -3,5 +3,5 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: reporting-api-report-only-sends-reports-on-violation={{$id:uuid()}}; Path=/content-security-policy/reporting-api -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] } +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}" }] } Content-Security-Policy-Report-Only: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-only-sends-reports-to-first-endpoint.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-only-sends-reports-to-first-endpoint.https.sub.html.sub.headers index 2053a81c28d..98b376c1768 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-only-sends-reports-to-first-endpoint.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-only-sends-reports-to-first-endpoint.https.sub.html.sub.headers @@ -4,4 +4,4 @@ Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: reporting-api-report-to-only-sends-reports-to-first-endpoint={{$id:uuid()}}; Path=/content-security-policy/reporting-api Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group csp-group-2 -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{uuid()}}" }] }, { "group": "csp-group-2", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] } +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{uuid()}}" }] }, { "group": "csp-group-2", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}" }] } diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers index b6504f6275b..0a06ef4f056 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-1.https.sub.html.sub.headers @@ -3,5 +3,5 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: reporting-api-report-to-overrides-report-uri-1={{$id:uuid()}}; Path=/content-security-policy/reporting-api -Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-uri "/content-security-policy/support/report.py?op=put&reportID={{$id}}"; report-to csp-group -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id:uuid()}}" }] } +Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-uri "/reporting/resources/report.py?op=put&reportID={{$id}}"; report-to csp-group +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id:uuid()}}" }] } diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers index 98541e1cc16..97a738fd4b3 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-report-to-overrides-report-uri-2.https.sub.html.sub.headers @@ -3,5 +3,5 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: reporting-api-report-to-overrides-report-uri-2={{$id:uuid()}}; Path=/content-security-policy/reporting-api -Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group; report-uri "/content-security-policy/support/report.py?op=put&reportID={{$id}}" -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id:uuid()}}" }] } +Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group; report-uri "/reporting/resources/report.py?op=put&reportID={{$id}}" +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id:uuid()}}" }] } diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers index b57b94031ac..b981ab46ac0 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-sends-reports-on-violation.https.sub.html.sub.headers @@ -3,5 +3,5 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: reporting-api-sends-reports-on-violation={{$id:uuid()}}; Path=/content-security-policy/reporting-api -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] } +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}" }] } Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html.sub.headers index 13d0ce65c96..37b1fea45b8 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting-api/reporting-api-works-on-frame-src.https.sub.html.sub.headers @@ -2,5 +2,5 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: reporting-api-works-on-frame-src={{$id:uuid()}}; Path=/content-security-policy/reporting-api -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}" }] } +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}" }] } Content-Security-Policy: script-src 'self' 'unsafe-inline'; frame-src 'none'; report-to csp-group diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html index 204c1f32024..c28e9ae44a5 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html @@ -5,9 +5,9 @@ <script src="/resources/testharnessreport.js"></script> <title>When multiple report-uri endpoints for multiple policies are specified, each gets a report</title> <!-- CSP headers -Content-Security-Policy-Report-Only: img-src http://* https://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: img-src http://* https://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} -Content-Security-Policy-Report-Only: img-src http://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: img-src http://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html.sub.headers index b7affbe7d41..485b6832e7f 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/multiple-report-policies.html.sub.headers @@ -3,6 +3,6 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: multiple-report-policies={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy-Report-Only: img-src http://* https://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: img-src http://* https://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} Set-Cookie: multiple-report-policies-2={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy-Report-Only: img-src http://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy-Report-Only: img-src http://*; default-src 'self'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html index 910df20a4c2..01f60800edf 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html @@ -7,7 +7,7 @@ <!-- CSP headers Content-Security-Policy: img-src 'none'; style-src *; script-src 'self' 'unsafe-inline' -Content-Security-Policy-Report-Only: img-src *; style-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: img-src *; style-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html.sub.headers index 5d4c5dcc4f6..4d7e6f191ad 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-and-enforce.html.sub.headers @@ -4,4 +4,4 @@ Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-and-enforce={{$id:uuid()}}; Path=/content-security-policy/reporting/ Content-Security-Policy: img-src 'none'; style-src *; script-src 'self' 'unsafe-inline' -Content-Security-Policy-Report-Only: img-src *; style-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: img-src *; style-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html index 5a75ea1c0a1..681694f6918 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Data-uri images are reported correctly</title> <!-- CSP headers -Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html.sub.headers index 52e2f164415..22c0494019f 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-data-uri.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-blocked-data-uri={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html index 9d56cdbdd90..a2966dbafbe 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html @@ -6,7 +6,7 @@ <title>Cross-origin images are reported correctly</title> <!-- CSP headers Content-Security-Policy: script-src 'self' 'unsafe-inline' -Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID=$id +Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID=$id --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html.sub.headers index ef5073b3863..02ebafeefe2 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri-cross-origin.sub.html.sub.headers @@ -4,4 +4,4 @@ Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-blocked-uri-cross-origin={{$id:uuid()}}; Path=/content-security-policy/reporting/ Content-Security-Policy: script-src 'self' 'unsafe-inline' -Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html index 5eb9f297496..1cfff902a20 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html @@ -6,7 +6,7 @@ <title>Blocked relative images are reported correctly</title> <!-- CSP headers Content-Security-Policy: script-src 'self' 'unsafe-inline' -Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html.sub.headers index 6f4b37ef836..8fb2f58abaa 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-blocked-uri.html.sub.headers @@ -4,4 +4,4 @@ Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-blocked-uri={{$id:uuid()}}; Path=/content-security-policy/reporting/ Content-Security-Policy: script-src 'self' 'unsafe-inline' -Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy-Report-Only: img-src 'none'; script-src 'self' 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html index a034bdbd4fe..b8203e9d30d 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html @@ -6,7 +6,7 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <!-- CSP headers - Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri http://{{domains[www1]}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID=$id + Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri http://{{domains[www1]}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID=$id --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html.sub.headers index cb1acfcd121..f65bd9ebf3c 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-cross-origin-no-cookies.sub.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-cross-origin-no-cookies={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri http://{{domains[www1]}}:{{ports[http][0]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri http://{{domains[www1]}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html index 7a92f1b9556..e64269c2dea 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Test multiple violations cause multiple reports</title> <!-- CSP headers - Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}} + Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html.sub.headers index 904e2c64aae..f86f84b8b26 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-01.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-multiple-violations-01={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html index d9f7da33388..cc64f151a3b 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html @@ -6,7 +6,7 @@ <title>This tests that multiple violations on a page trigger multiple reports if and only if the violations are distinct.</title> <!-- CSP headers - Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}} + Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html.sub.headers index e7bf84b9448..e94e0dfa60d 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-multiple-violations-02.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-multiple-violations-02={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'self'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-in-meta.sub.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-in-meta.sub.html index 574c218b427..4df9865d2c4 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-in-meta.sub.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-in-meta.sub.html @@ -9,7 +9,7 @@ Content-Security-Policy: script-src 'unsafe-inline' 'self' --> <!-- since we try to set the report-uri in the meta tag, we have to set the cookie with the reportID in here instead of in the headers file --> - <meta http-equiv="Content-Security-Policy-Report-Only" content="img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id:uuid()}}"> + <meta http-equiv="Content-Security-Policy-Report-Only" content="img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id:uuid()}}"> </head> <body> <script> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html index 9effbc69d61..757db4f37b0 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html @@ -4,7 +4,7 @@ <script nonce='abc' src="/resources/testharness.js"></script> <script nonce='abc' src="/resources/testharnessreport.js"></script> <!-- CSP headers -Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'nonce-abc'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'nonce-abc'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html.sub.headers index 549f3dbaa2e..5ca4a65261d 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-only-unsafe-eval.html.sub.headers @@ -1,4 +1,4 @@ Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Set-Cookie: report-only-unsafe-eval={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'nonce-abc'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: script-src 'unsafe-inline' 'nonce-abc'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url-on-mixed-content-frame.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url-on-mixed-content-frame.https.sub.html.sub.headers index d079fb134a5..50b5438c4b7 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url-on-mixed-content-frame.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url-on-mixed-content-frame.https.sub.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-original-url-on-mixed-content-frame={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: block-all-mixed-content; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: block-all-mixed-content; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html index 5c9c4566bf7..f95f7e3e6b0 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html @@ -4,7 +4,7 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <!-- CSP headers - Content-Security-Policy: img-src {{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}; script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID=$id + Content-Security-Policy: img-src {{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}; script-src 'unsafe-inline' 'self'; report-uri /reporting/resources/report.py?op=put&reportID=$id --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html.sub.headers index 1031a7a00a9..b695417aefd 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-original-url.sub.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-original-url={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: img-src {{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}; script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: img-src {{location[scheme]}}://{{domains[www1]}}:{{ports[http][0]}}; script-src 'unsafe-inline' 'self'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html index 9a09722b409..aa2ec6bd9d4 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Cookies are sent on same origin violation reports</title> <!-- CSP headers - Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /content-security-policy/support/report.py?op=put&reportID={{$id}} + Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html.sub.headers index 356439db435..23fb823730e 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-same-origin-with-cookies.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-same-origin-with-cookies={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /content-security-policy/support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: script-src 'unsafe-inline' 'self'; img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html index 1d959fd4abc..0143d1bc82d 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Violation report is sent if violation occurs.</title> <!-- CSP headers - Content-Security-Policy: default-src 'self'; report-uri ../support/report.py?op=put&reportID={{$id}} + Content-Security-Policy: default-src 'self'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html.sub.headers index 1fb9842c8aa..9b8c3d0fdbc 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-effective-directive.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-uri-effective-directive={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: default-src 'self'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: default-src 'self'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html index ca65c9054a8..1cb5a2c659c 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Violation report is sent from inline javascript.</title> <!-- CSP headers - Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}} + Content-Security-Policy: img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html.sub.headers index 07239a066a6..fd2913a39b5 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-inline-javascript.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-uri-from-inline-javascript={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html index 354c69644cf..d5358111256 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Violation report is sent from javascript resource.</title> <!-- CSP headers - Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}} + Content-Security-Policy: img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html.sub.headers index 9443226937a..faa23708e54 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-from-javascript.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-uri-from-javascript={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: img-src 'none'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: img-src 'none'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html index 499d1195370..5bbdc01a53c 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Content-Security-Policy-Report-Only violation report is sent even when resource is blocked by actual policy.</title> <!-- CSP headers - Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}} + Content-Security-Policy-Report-Only: img-src http://*; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} Content-Security-Policy: img-src http://* --> </head> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html.sub.headers index 0c128872102..172c36dee00 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple-reversed.html.sub.headers @@ -3,5 +3,5 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-uri-multiple-reversed={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}} -Content-Security-Policy: img-src http://*
\ No newline at end of file +Content-Security-Policy-Report-Only: img-src http://*; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} +Content-Security-Policy: img-src http://* diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html index 268da91296d..190c9ee31e2 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html @@ -6,7 +6,7 @@ <title>Content-Security-Policy-Report-Only violation report is sent even when resource is blocked by actual policy.</title> <!-- CSP headers Content-Security-Policy: img-src http://* - Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}} + Content-Security-Policy-Report-Only: img-src http://*; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html.sub.headers index d78c8e50fb4..cf1073823d2 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-multiple.html.sub.headers @@ -4,4 +4,4 @@ Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-uri-multiple={{$id:uuid()}}; Path=/content-security-policy/reporting/ Content-Security-Policy: img-src http://* -Content-Security-Policy-Report-Only: img-src http://*; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy-Report-Only: img-src http://*; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html index e64d797f9a9..406238ead7e 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html @@ -5,7 +5,7 @@ <script src="/resources/testharnessreport.js"></script> <title>Relative scheme URIs are accepted as the report-uri.</title> <!-- CSP headers - Content-Security-Policy: script-src 'self'; report-uri //{{location[host]}}/content-security-policy/support/report.py?op=put&reportID={{$id}} + Content-Security-Policy: script-src 'self'; report-uri //{{location[host]}}/reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html.sub.headers index 74f263dea50..97e302a4b71 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/report-uri-scheme-relative.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: report-uri-scheme-relative={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: script-src 'self'; report-uri //{{location[host]}}/content-security-policy/support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: script-src 'self'; report-uri //{{location[host]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/support/generate-csp-report.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/reporting/support/generate-csp-report.html.sub.headers index 6d1eedb1fcb..7993b3e286c 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/support/generate-csp-report.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/support/generate-csp-report.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: generate-csp-report={{$id:uuid()}}; Path=/content-security-policy/reporting/ -Content-Security-Policy: script-src 'self' 'nonce-abc'; report-uri ../../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy: script-src 'self' 'nonce-abc'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/reporting/support/not-embeddable-frame.py b/tests/wpt/web-platform-tests/content-security-policy/reporting/support/not-embeddable-frame.py index c650d613524..9e65b424359 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/reporting/support/not-embeddable-frame.py +++ b/tests/wpt/web-platform-tests/content-security-policy/reporting/support/not-embeddable-frame.py @@ -5,6 +5,6 @@ def main(request, response): csp_header = b'Content-Security-Policy-Report-Only' \ if request.GET.first(b'reportOnly', None) == b'true' else b'Content-Security-Policy' - headers.append((csp_header, b"frame-ancestors 'none'; report-uri ../../support/report.py?op=put&reportID=" + request.GET[b'reportID'])) + headers.append((csp_header, b"frame-ancestors 'none'; report-uri /reporting/resources/report.py?op=put&reportID=" + request.GET[b'reportID'])) return headers, b'{}' diff --git a/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html b/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html index 5357aa2eef2..001d1752462 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html +++ b/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html @@ -2,7 +2,7 @@ <head> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> - <!-- Content-Security-Policy-Report-Only: script-src 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} --> + <!-- Content-Security-Policy-Report-Only: script-src 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> <script> diff --git a/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html.sub.headers index 37a04b5fc2e..e312b2c83c7 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/script-src/eval-allowed-in-report-only-mode-and-sends-report.html.sub.headers @@ -1,2 +1,2 @@ Set-Cookie: eval-allowed-in-report-only-mode-and-sends-report={{$id:uuid()}}; Path=/content-security-policy/script-src -Content-Security-Policy-Report-Only: script-src 'unsafe-inline'; report-uri ../support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: script-src 'unsafe-inline'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/script-src/javascript-window-open-blocked.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/script-src/javascript-window-open-blocked.html.sub.headers index 32fc4d54a32..b54c91e74e7 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/script-src/javascript-window-open-blocked.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/script-src/javascript-window-open-blocked.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: javascript-window-open-blocked={{$id:uuid()}}; Path=/content-security-policy/script-src/ -Content-Security-Policy: script-src 'nonce-abc'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: script-src 'nonce-abc'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/style-src/style-src-multiple-policies-multiple-hashing-algorithms.html.sub.headers b/tests/wpt/web-platform-tests/content-security-policy/style-src/style-src-multiple-policies-multiple-hashing-algorithms.html.sub.headers index 8d83a347513..e31aa5aa270 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/style-src/style-src-multiple-policies-multiple-hashing-algorithms.html.sub.headers +++ b/tests/wpt/web-platform-tests/content-security-policy/style-src/style-src-multiple-policies-multiple-hashing-algorithms.html.sub.headers @@ -3,5 +3,5 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: style-src-multiple-policies-multiple-hashing-algorithms={{$id:uuid()}}; Path=/content-security-policy/style-src/ -Content-Security-Policy: style-src 'sha256-rB6kiow2O3eFUeTNyyLeK3wV0+l7vNB90J1aqllKvjg='; script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}} -Content-Security-Policy: style-src 'sha384-DAShdG5sejEaOdWfT+TQMRP5mHssKiUNjFggNnElIvIoj048XQlacVRs+za2AM1a'; script-src 'unsafe-inline' 'self'; report-uri ../support/report.py?op=put&reportID={{$id}}
\ No newline at end of file +Content-Security-Policy: style-src 'sha256-rB6kiow2O3eFUeTNyyLeK3wV0+l7vNB90J1aqllKvjg='; script-src 'unsafe-inline' 'self'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} +Content-Security-Policy: style-src 'sha384-DAShdG5sejEaOdWfT+TQMRP5mHssKiUNjFggNnElIvIoj048XQlacVRs+za2AM1a'; script-src 'unsafe-inline' 'self'; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/content-security-policy/support/checkReport.sub.js b/tests/wpt/web-platform-tests/content-security-policy/support/checkReport.sub.js index 3137b926ae8..f188d18207e 100644 --- a/tests/wpt/web-platform-tests/content-security-policy/support/checkReport.sub.js +++ b/tests/wpt/web-platform-tests/content-security-policy/support/checkReport.sub.js @@ -48,7 +48,7 @@ // received to conclude that no report has been generated. These timeouts must // not exceed the test timeouts set by vendors otherwise the test would fail. var timeout = document.querySelector("meta[name=timeout][content=long]") ? 20 : 3; - var reportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=retrieve_report&timeout=" + timeout + "&reportID=" + reportID; + var reportLocation = location.protocol + "//" + location.host + "/reporting/resources/report.py?op=retrieve_report&timeout=" + timeout + "&reportID=" + reportID; if (testName == "") testName = "Violation report status OK."; var reportTest = async_test(testName); @@ -107,7 +107,7 @@ } cookieTest.done(); }); - var cReportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=retrieve_cookies&timeout=" + timeout + "&reportID=" + reportID; + var cReportLocation = location.protocol + "//" + location.host + "/reporting/resources/report.py?op=retrieve_cookies&timeout=" + timeout + "&reportID=" + reportID; cookieReport.open("GET", cReportLocation, true); cookieReport.send(); } @@ -122,7 +122,7 @@ reportCountTest.done(); }); - var cReportLocation = location.protocol + "//" + location.host + "/content-security-policy/support/report.py?op=retrieve_count&timeout=" + timeout + "&reportID=" + reportID; + var cReportLocation = location.protocol + "//" + location.host + "/reporting/resources/report.py?op=retrieve_count&timeout=" + timeout + "&reportID=" + reportID; reportCountReport.open("GET", cReportLocation, true); reportCountReport.send(); } diff --git a/tests/wpt/web-platform-tests/content-security-policy/support/report.py b/tests/wpt/web-platform-tests/content-security-policy/support/report.py deleted file mode 100644 index 088f3c9b379..00000000000 --- a/tests/wpt/web-platform-tests/content-security-policy/support/report.py +++ /dev/null @@ -1,64 +0,0 @@ -import time -import json -import re - -from wptserve.utils import isomorphic_decode - -def retrieve_from_stash(request, key, timeout, default_value): - t0 = time.time() - while time.time() - t0 < timeout: - time.sleep(0.5) - value = request.server.stash.take(key=key) - if value is not None: - return value - - return default_value - -def main(request, response): - op = request.GET.first(b"op") - key = request.GET.first(b"reportID") - cookie_key = re.sub(b'^....', b'cccc', key) - count_key = re.sub(b'^....', b'dddd', key) - - try: - timeout = request.GET.first(b"timeout") - except: - timeout = 0.5 - timeout = float(timeout) - - if op == b"retrieve_report": - return [(b"Content-Type", b"application/json")], retrieve_from_stash(request, key, timeout, json.dumps({u'error': u'No such report.', u'guid' : isomorphic_decode(key)})) - - if op == b"retrieve_cookies": - return [(b"Content-Type", b"application/json")], u"{ \"reportCookies\" : " + str(retrieve_from_stash(request, cookie_key, timeout, u"\"None\"")) + u"}" - - if op == b"retrieve_count": - return [(b"Content-Type", b"application/json")], json.dumps({u'report_count': str(retrieve_from_stash(request, count_key, timeout, 0))}) - - # save cookies - if len(request.cookies.keys()) > 0: - # convert everything into strings and dump it into a dict so it can be jsoned - temp_cookies_dict = {} - for dict_key in request.cookies.keys(): - temp_cookies_dict[isomorphic_decode(dict_key)] = str(request.cookies.get_list(dict_key)) - with request.server.stash.lock: - request.server.stash.take(key=cookie_key) - request.server.stash.put(key=cookie_key, value=json.dumps(temp_cookies_dict)) - - # save latest report - report = request.body - report.rstrip() - with request.server.stash.lock: - request.server.stash.take(key=key) - request.server.stash.put(key=key, value=report) - - with request.server.stash.lock: - # increment report count - count = request.server.stash.take(key=count_key) - if count is None: - count = 0 - count += 1 - request.server.stash.put(key=count_key, value=count) - - # return acknowledgement report - return [(b"Content-Type", b"text/plain")], b"Recorded report " + report diff --git a/tests/wpt/web-platform-tests/cookies/attributes/domain/domain.sub.html b/tests/wpt/web-platform-tests/cookies/attributes/domain.sub.html index 96e2ce071d0..96e2ce071d0 100644 --- a/tests/wpt/web-platform-tests/cookies/attributes/domain/domain.sub.html +++ b/tests/wpt/web-platform-tests/cookies/attributes/domain.sub.html diff --git a/tests/wpt/web-platform-tests/cookies/http-state/ordering-tests.html b/tests/wpt/web-platform-tests/cookies/http-state/ordering-tests.html deleted file mode 100644 index 4dce985ac71..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/ordering-tests.html +++ /dev/null @@ -1,30 +0,0 @@ -<!doctype html> -<html> - <head> - <meta charset=utf-8> - <title>Tests basic cookie setting functionality</title> - <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8"> - - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> - <script src="resources/cookie-http-state-template.js"></script> - </head> - <body> - <div id="log"></div> - <div id="iframes"></div> - <script> - setup({ explicit_timeout: true }); - - const TEST_CASES = [ - {file: "ordering0001", name: "ordering0001"}, - ]; - - for (const i in TEST_CASES) { - const t = TEST_CASES[i]; - promise_test(createCookieTest(t.file), - t.file + " - " + t.name); - } - - </script> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/all-tests.html.py-str b/tests/wpt/web-platform-tests/cookies/http-state/resources/all-tests.html.py-str deleted file mode 100644 index 474e0cb2cd5..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/all-tests.html.py-str +++ /dev/null @@ -1,71 +0,0 @@ -<!doctype html> -<html> - <head> - <meta charset=utf-8> - <title>Tests basic cookie setting functionality</title> - <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8"> - - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> - <script src="cookie-http-state-template.js"></script> - </head> - <body> - <h1>Cookie Tests</h1> - This page is a debug page for cookie tests. To run a test in isolation, - append "?debug=" and the test id to this URL. E.g. to debug "value0001", use - this url:<br> - <a href="cookie-setter.py?debug=value0001"> - cookie-setter.py?debug=value0001 - </a><br> - <br> - To request, append "?file=" and the file name (stripped of - "-tests.html"). For value0001, this would be:<br> - <a href="cookie-setter.py?file=value0001"> - cookie-setter.py?file=value0001 - </a><br> - <br> - Please note, the general tests are one level higher:<br> - <a href="../attribute-tests.html"> ../attribute-tests.html </a><br> - <a href="../charset-tests.html"> ../charset-tests.html </a><br> - <a href="../chromium-tests.html"> ../chromium-tests.html </a><br> - <a href="../comma-tests.html"> ../comma-tests.html </a><br> - <a href="../domain-tests.html"> ../domain-tests.html </a><br> - <a href="../general-tests.html"> ../general-tests.html </a><br> - <a href="../mozilla-tests.html"> ../mozilla-tests.html </a><br> - <a href="../name-tests.html"> ../name-tests.html </a><br> - <a href="../ordering-tests.html"> ../ordering-tests.html </a><br> - <a href="../path-tests.html"> ../path-tests.html </a><br> - <a href="../value-tests.html"> ../value-tests.html </a><br> - - <h2>Current Cookies</h2> - These are all cookies, currently set by the browser for this domain: <br> - <span id="cookies">(None)</span> - <script type="text/javascript"> - setInterval(() => { - document.getElementById("cookies").textContent = - document.cookie || "(None)"; - }, 500); - </script> - - <h2>All Tests</h2> - Below, all tests are running. The Log contains messages (e.g. when cookies - are left over) and the IFrames list preserves all requested files and shows - which cookies were expected to show. <br> - <div id="log"></div> - <div id="iframes"></div> - - <script> - setup({ explicit_timeout: true }); - - const TEST_CASES = [%s]; - - for (const i in TEST_CASES) { - const t = TEST_CASES[i]; - promise_test(createCookieTest(t.file), - t.file + " - " + t.name); - } - - - </script> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/cookie-http-state-template.js b/tests/wpt/web-platform-tests/cookies/http-state/resources/cookie-http-state-template.js deleted file mode 100644 index b2af226e22e..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/cookie-http-state-template.js +++ /dev/null @@ -1,131 +0,0 @@ -const SERVER_LOCATION = "resources"; -const SERVER_SCRIPT = SERVER_LOCATION + "/cookie-setter.py"; - -/* Adds a div with "<time> [<tag>] - <message>" to the "#log" container.*/ -function log(message, tag) { - let log_str = document.createElement('div'); - log_str.textContent = new Date().toTimeString().replace(/\s.+$/, ''); - if (tag) { - log_str.textContent += " [" + tag + "] "; - } - log_str.textContent += " - " + message; - let log_container = document.getElementById("log"); - log_container.appendChild(log_str); - log_container.scrollTo(0, log_container.scrollHeight); -} - -/* Removes the "Cookie: " prefix and strip any leading or trailing whitespace.*/ -function stripPrefixAndWhitespace(cookie_text) { - return cookie_text.replace(/^Cookie: /, '').replace(/^\s+|\s+$/g, ''); -} - -/* Returns the absolute path of the resource folder, ignoring any navigation. */ -function getLocalResourcesPath() { - let replace = "(" + SERVER_LOCATION + "\/)*"; // Redundant location. - replace += "[^\/]*$"; // Everything after the last "/". - return location.pathname.replace(new RegExp(replace), "") + SERVER_LOCATION; -} - -/* Returns the absolute server location ignoring any navgation.*/ -function getAbsoluteServerLocation() { - // Replace the server location and everything coming after it ... - let replace = SERVER_LOCATION + ".*$"; - // ... with the Server script (which includes the server location). - return getLocalResourcesPath().replace(new RegExp(replace),'')+ SERVER_SCRIPT; -} - -/* Expires a cookie by name by setting it's expiry date into the past.*/ -function expireCookie(name, expiry_date, path) { - name = name || ""; - expiry_date = expiry_date || "Thu, 01 Jan 1970 00:00:00 UTC"; - path = path || getLocalResourcesPath(); - document.cookie = name + "=value; expires=" + expiry_date + "; path=" + path + ";"; -} - -/* Captures a snapshot of cookies with |parse| and allows to diff it with a -second snapshot with |diffWith|. This allows to run tests even if cookies were -previously set that would mess with the expected final set of Cookies. -With |resetCookies|, all cookies set between first and second snapshot are -expired. */ -function CookieManager() { - this.initial_cookies = []; -} - -/* Creates a snapshot of the current given document cookies.*/ -CookieManager.prototype.parse = document_cookies => { - this.initial_cookies = []; - document_cookies = document_cookies.replace(/^Cookie: /, ''); - if (document_cookies != "") { - this.initial_cookies = document_cookies.split(/\s*;\s*/); - } -} - -/* Creates a diff of newly added cookies between the initial snapshot and the -newly given cookies. The diff is stored for cleaning purposes. A second call -will replace the the stored diff entirely.*/ -CookieManager.prototype.diffWith = document_cookies => { - this.actual_cookies = document_cookies; - for (let i in initial_cookies) { - let no_spaces_cookie_regex = - new RegExp(/\s*[\;]*\s/.source + initial_cookies[i].replace(/\\/, "\\\\")); - this.actual_cookies = this.actual_cookies.replace(no_spaces_cookie_regex, ''); - } - return this.actual_cookies; -} - -/* Cleans cookies between the first and the second snapshot. -Some tests might set cookies to the root path or cookies without key. Both cases -are dropped here.*/ -CookieManager.prototype.resetCookies = () => { - // If a cookie without keys was accepted, drop it additionally. - let cookies_to_delete = [""].concat(this.actual_cookies.split(/\s*;\s*/)) - for (let i in cookies_to_delete){ - expireCookie(cookies_to_delete[i].replace(/=.*$/, "")); - // Drop cookies with same name that were set to the root path which happens - // for example due to "0010" still failing. - expireCookie(cookies_to_delete[i].replace(/=.*$/, ""), - /*expiry_date=*/null, - /*path=*/'/'); - // Some browsers incorrectly include the final "forward slash" character - // when calculating the default path. The expected behavior for default - // path calculation is verified elsewhere; this utility accommodates the - // non-standard behavior in order to improve the focus of the test suite. - expireCookie(cookies_to_delete[i].replace(/=.*$/, ""), - /*expiry_date=*/null, - /*path=*/getLocalResourcesPath() + "/"); - } -} - -/* Returns a new cookie test. -The test creates an iframe where a |file| from the cookie-setter.py is loaded. -This sets cookies which are diffed with an initial cookie snapshot and compared -to the expectation that the server returned. -Finally, it cleans up newly set cookies and all cookies in the root path or -without key. */ -function createCookieTest(file) { - return t => { - let iframe_container = document.getElementById("iframes"); - const iframe = document.createElement('iframe'); - iframe_container.appendChild(iframe); - iframe_container.scrollTo(0, iframe_container.scrollHeight); - let diff_tool = new CookieManager(); - t.add_cleanup(diff_tool.resetCookies); - return new Promise((resolve, reject) => { - diff_tool.parse(document.cookie); - if (diff_tool.initial_cookies.length > 0) { - // The cookies should ideally be empty. If that isn't the case, log it. - //Cookies with equal keys (e.g. foo=) may have unwanted side-effects. - log("Run with existing cookies: " + diff_tool.initial_cookies, file); - } - window.addEventListener("message", t.step_func(e => { - assert_true(!!e.data, "Message contains data") - resolve(e.data); - })); - iframe.src = getAbsoluteServerLocation() + "?file=" + file; - }).then((response) => { - let actual_cookies = diff_tool.diffWith(response.cookies); - let expected_cookies = stripPrefixAndWhitespace(response.expectation); - assert_equals(actual_cookies, expected_cookies); - }); - } -}; diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/cookie-setter.py b/tests/wpt/web-platform-tests/cookies/http-state/resources/cookie-setter.py deleted file mode 100644 index 187f03ea4a1..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/cookie-setter.py +++ /dev/null @@ -1,96 +0,0 @@ -from os import path -from os import listdir - -from wptserve.utils import isomorphic_decode - -""" -The main purpose of this script is to set cookies based on files in this folder: - cookies/http-state/resources - -If a wpt server is running, navigate to - http://<server>/cookies/http-state/resources/cookie-setter.py -which will run all cookie tests and explain the usage of this script in detail. - -If you want to run a test in isolation, append "?debug=" and the test id to the -URL above. -""" - -SETUP_FILE_TEMPLATE = u"{}-test" -EXPECTATION_FILE_TEMPLATE = u"{}-expected" -EXPECTATION_HTML_SCAFFOLD = u"iframe-expectation-doc.html.py-str" -DEBUGGING_HTML_SCAFFOLD = u"debugging-single-test.html.py-str" -DEFAULT_RESOURCE_DIR = path.join(u"cookies", u"http-state", u"resources") -DEFAULT_TEST_DIR = u"test-files" -ALL_TESTS = u"all-tests.html.py-str" - - -def dump_file(directory, filename): - """Reads a file into a byte string.""" - return open(path.join(directory, filename), "rb").read() - - -class CookieTestResponse(object): - """Loads the Set-Cookie header from a given file name. Relies on the naming - convention that ever test file is called '<test_id>-test' and every - expectation is called '<test_id>-expected'.""" - def __init__(self, file, root): - super(CookieTestResponse, self).__init__() - self.__test_file = SETUP_FILE_TEMPLATE.format(file) - self.__expectation_file = EXPECTATION_FILE_TEMPLATE.format(file) - self.__resources_dir = path.join(root, DEFAULT_RESOURCE_DIR) - self.__test_files_dir = path.join(self.__resources_dir, DEFAULT_TEST_DIR) - - def cookie_setting_header(self): - """Returns the loaded header.""" - return dump_file(self.__test_files_dir, self.__test_file) - - def body_with_expectation(self): - """Returns a full HTML document which contains the expectation.""" - html_frame = dump_file(self.__resources_dir, EXPECTATION_HTML_SCAFFOLD) - expected_data = dump_file(self.__test_files_dir, self.__expectation_file) - return html_frame.replace(b"{data}", expected_data) - -def find_all_test_files(root): - """Retrieves all files from the test-files/ folder and returns them as - string pair as used in the JavaScript object. Sorted by filename.""" - all_files = [] - line_template = u'{{file: "{filename}", name: "{filename}"}},' - for file in listdir(path.join(root, DEFAULT_RESOURCE_DIR, DEFAULT_TEST_DIR)): - if file.endswith(u'-test'): - name = file.replace(u'-test', u'') - all_files.append(line_template.format(filename=name).encode()) - all_files.sort() - return all_files - -def generate_for_all_tests(root): - """Returns an HTML document which loads and executes all tests in the - test-files/ directory.""" - html_frame = dump_file(path.join(root, DEFAULT_RESOURCE_DIR), ALL_TESTS) - return html_frame % b'\n'.join(find_all_test_files(root)) - -def main(request, response): - if b"debug" in request.GET: - """If '?debug=' is set, return the document for a single test.""" - response.writer.write_status(200) - response.writer.end_headers() - html_frame = dump_file(path.join(request.doc_root, DEFAULT_RESOURCE_DIR), - DEBUGGING_HTML_SCAFFOLD) - test_file = html_frame % (request.GET[b'debug']) - response.writer.write_content(test_file) - return - - if b"file" in request.GET: - """If '?file=' is set, send a cookie and a document which contains the - expectation of which cookies should be set by the browser in response.""" - cookie_response = CookieTestResponse(isomorphic_decode(request.GET[b'file']), request.doc_root) - - response.writer.write_status(200) - response.writer.write(cookie_response.cookie_setting_header()) - response.writer.end_headers() - response.writer.write_content(cookie_response.body_with_expectation()) - return - - """Without any arguments, return documentation and run all available tests.""" - response.writer.write_status(200) - response.writer.end_headers() - response.writer.write_content(generate_for_all_tests(request.doc_root)) diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/debugging-single-test.html.py-str b/tests/wpt/web-platform-tests/cookies/http-state/resources/debugging-single-test.html.py-str deleted file mode 100644 index a7b25b3d1bf..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/debugging-single-test.html.py-str +++ /dev/null @@ -1,21 +0,0 @@ -<!doctype html> -<html> - <head> - <meta charset=utf-8> - <title>Debug single test</title> - <meta name=help href="https://tools.ietf.org/html/rfc6265#page-8"> - - <script src="/resources/testharness.js"></script> - <script src="/resources/testharnessreport.js"></script> - <script src="cookie-http-state-template.js"></script> - </head> - <body> - <div id="log"></div> - <div id="iframes"></div> - <script> - setup({ explicit_timeout: true }); - - promise_test(createCookieTest("%s"), "DEBUG"); - </script> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/iframe-content-pushing.js b/tests/wpt/web-platform-tests/cookies/http-state/resources/iframe-content-pushing.js deleted file mode 100644 index 99ede2a2582..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/iframe-content-pushing.js +++ /dev/null @@ -1,4 +0,0 @@ -window.top.postMessage({ - "cookies": document.cookie, - "expectation": document.querySelector('#data').innerText -}, "*"); diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/iframe-expectation-doc.html.py-str b/tests/wpt/web-platform-tests/cookies/http-state/resources/iframe-expectation-doc.html.py-str deleted file mode 100644 index 9b782a15f84..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/iframe-expectation-doc.html.py-str +++ /dev/null @@ -1,11 +0,0 @@ -<!doctype html> -<html> - <head> - <meta charset=utf-8> - <title>Cookie Test Expectation Document</title> - </head> - <body> - <div id="data" style="white-space: pre">{data}</div> - <script src="iframe-content-pushing.js"></script> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/test-files/ordering0001-expected b/tests/wpt/web-platform-tests/cookies/http-state/resources/test-files/ordering0001-expected deleted file mode 100644 index 3d819593f53..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/test-files/ordering0001-expected +++ /dev/null @@ -1 +0,0 @@ -Cookie: key=val5; key=val1; key=val2; key=val4 diff --git a/tests/wpt/web-platform-tests/cookies/http-state/resources/test-files/ordering0001-test b/tests/wpt/web-platform-tests/cookies/http-state/resources/test-files/ordering0001-test deleted file mode 100644 index ba6e85c44cd..00000000000 --- a/tests/wpt/web-platform-tests/cookies/http-state/resources/test-files/ordering0001-test +++ /dev/null @@ -1,7 +0,0 @@ -Set-Cookie: key=val0; -Set-Cookie: key=val1; path=/cookie-parser-result -Set-Cookie: key=val2; path=/ -Set-Cookie: key=val3; path=/bar -Set-Cookie: key=val4; domain=.example.org -Set-Cookie: key=val5; domain=.example.org; path=/cookie-parser-result/foo -Location: /cookie-parser-result/foo/baz?ordering0001 diff --git a/tests/wpt/web-platform-tests/cookies/ordering/ordering.sub.html b/tests/wpt/web-platform-tests/cookies/ordering/ordering.sub.html new file mode 100644 index 00000000000..19976328db9 --- /dev/null +++ b/tests/wpt/web-platform-tests/cookies/ordering/ordering.sub.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie ordering</title> + <meta name=help href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-5.5"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + </head> + <body> + <script> + const port = "{{ports[http][0]}}"; + const wwwHost = "{{domains[www]}}"; + + test(t => { + const win = window.open(`http://${wwwHost}:${port}/cookies/ordering/resources/ordering-child.sub.html`); + fetch_tests_from_window(win); + }); + </script> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/cookies/ordering/resources/ordering-child.sub.html b/tests/wpt/web-platform-tests/cookies/ordering/resources/ordering-child.sub.html new file mode 100644 index 00000000000..d99ccfba7e1 --- /dev/null +++ b/tests/wpt/web-platform-tests/cookies/ordering/resources/ordering-child.sub.html @@ -0,0 +1,74 @@ +<!doctype html> +<html> + <head> + <meta charset=utf-8> + <title>Test cookie ordering</title> + <meta name=help href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-5.5"> + <meta name="timeout" content="long"> + <script src="/resources/testharness.js"></script> + <script src="/cookies/resources/cookie-test.js"></script> + </head> + <body> + <div id=log></div> + <script> + const host = "{{host}}"; + const wwwHost = "{{domains[www]}}"; + + const orderingTests = [ + { + cookie: [ + "testA=1", + "testB=1; path=/cookies", + "testC=1; path=/", + "testD=1; path=/cooking", + `testE=1; domain=.${host}; path=/`, + `testF=1; domain=.${host}; path=/cookies/attributes`, + ], + expected: "testF=1; testB=1; testC=1; testE=1", + name: "Cookies with longer path attribute values are ordered before shorter ones", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: [ + "testA=2", + "testB=2; path=/cookies/attributes/resources", + "testC=2; path=/", + "testD=2; path=/cooking", + `testE=2; domain=.${host}`, + `testF=2; domain=.${host}; path=/cookies/attributes`, + `testG=2; domain=.${host}; path=/cookies/attributes/resources/path`, + "testH=2; path=/cookies", + ], + expected: "testG=2; testB=2; testF=2; testH=2; testC=2", + name: "Cookies with longer path attribute values are ordered before shorter ones (2)", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: [ + "testA=3; path=/cookies/attributes/resources/path", + "testB=3; path=/cookies/attributes/resources/path/one.html", + "testC=3; path=/cookies/attributes", + ], + expected: "testB=3; testA=3; testC=3", + name: "Cookies with longer paths are listed before cookies with shorter paths", + location: "/cookies/attributes/resources/path/one.html", + }, + { + cookie: [ + "testZ=4; path=/cookies", + "testB=4; path=/cookies/attributes/resources/path", + "testA=4; path=/cookies", + ], + expected: "testB=4; testZ=4; testA=4", + name: "For equal length paths, list the cookie with an earlier creation time first", + location: "/cookies/attributes/resources/path/one.html", + }, + ]; + + for (const test of orderingTests) { + httpRedirectCookieTest(test.cookie, test.expected, test.name, + test.location); + } + </script> + </body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/cookies/resources/cookie-helper.sub.js b/tests/wpt/web-platform-tests/cookies/resources/cookie-helper.sub.js index a0ded48f2a8..789d38d1eb4 100644 --- a/tests/wpt/web-platform-tests/cookies/resources/cookie-helper.sub.js +++ b/tests/wpt/web-platform-tests/cookies/resources/cookie-helper.sub.js @@ -35,6 +35,12 @@ function redirectTo(origin, url) { return origin + "/cookies/resources/redirectWithCORSHeaders.py?status=307&location=" + encodeURIComponent(url); } +// Returns a URL on |origin| which navigates the window to the given URL (by +// setting window.location). +function navigateTo(origin, url) { + return origin + "/cookies/resources/navigate.html?location=" + encodeURIComponent(url); +} + // Asserts that `document.cookie` contains or does not contain (according to // the value of |present|) a cookie named |name| with a value of |value|. function assert_dom_cookie(name, value, present) { diff --git a/tests/wpt/web-platform-tests/cookies/resources/navigate.html b/tests/wpt/web-platform-tests/cookies/resources/navigate.html new file mode 100644 index 00000000000..077efba5699 --- /dev/null +++ b/tests/wpt/web-platform-tests/cookies/resources/navigate.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script> + // Navigates the window to a location specified via URL query param. + const params = new URLSearchParams(window.location.search); + const loc = params.get('location'); + window.location = loc; +</script> diff --git a/tests/wpt/web-platform-tests/cookies/samesite/iframe.https.html b/tests/wpt/web-platform-tests/cookies/samesite/iframe.https.html index 04ebb95836d..3c7b638810e 100644 --- a/tests/wpt/web-platform-tests/cookies/samesite/iframe.https.html +++ b/tests/wpt/web-platform-tests/cookies/samesite/iframe.https.html @@ -50,7 +50,7 @@ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain redirecting to same-host fetches are strictly same-site"); create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Cross-site redirecting to same-host fetches are strictly same-site"); - // Redirect from {same-host,subdomain,cross-site} to same-host: + // Redirect from {same-host,subdomain,cross-site} to subdomain: create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host redirecting to subdomain fetches are strictly same-site"); create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain redirecting to subdomain fetches are strictly same-site"); create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Cross-site redirecting to subdomain fetches are strictly same-site"); @@ -59,4 +59,19 @@ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site fetches are cross-site"); create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site fetches are cross-site"); create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site fetches are cross-site"); + + // Navigate from {same-host,subdomain,cross-site} to same-host: + create_test(SECURE_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host navigating to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain navigating to same-host fetches are strictly same-site"); + create_test(SECURE_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site navigating to same-host fetches are cross-site"); + + // Navigate from {same-host,subdomain,cross-site} to subdomain: + create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host navigating to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain navigating to subdomain fetches are strictly same-site"); + create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site navigating to subdomain fetches are cross-site-site"); + + // Navigate from {same-host,subdomain,cross-site} to cross-site: + create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Same-host navigating to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Subdomain navigating to cross-site fetches are cross-site"); + create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site navigating to cross-site fetches are cross-site"); </script> diff --git a/tests/wpt/web-platform-tests/cookies/samesite/resources/echo-cookies.html b/tests/wpt/web-platform-tests/cookies/samesite/resources/echo-cookies.html index 9b8b286015f..a1b29b9b03c 100644 --- a/tests/wpt/web-platform-tests/cookies/samesite/resources/echo-cookies.html +++ b/tests/wpt/web-platform-tests/cookies/samesite/resources/echo-cookies.html @@ -1,5 +1,8 @@ <!DOCTYPE html> <meta charset="utf-8"> <script> - window.opener.postMessage({ type: 'COOKIES_SET', cookies: document.cookie }, '*'); + if (window.opener) + window.opener.postMessage({ type: 'COOKIES_SET', cookies: document.cookie }, '*'); + if (window.parent !== window) + window.parent.postMessage({ type: 'FRAME_COOKIES_SET', cookies: document.cookie }, '*'); </script> diff --git a/tests/wpt/web-platform-tests/cookies/samesite/resources/navigate-iframe.html b/tests/wpt/web-platform-tests/cookies/samesite/resources/navigate-iframe.html new file mode 100644 index 00000000000..98ad6264fab --- /dev/null +++ b/tests/wpt/web-platform-tests/cookies/samesite/resources/navigate-iframe.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/cookies/resources/cookie-helper.sub.js"></script> +<script> + window.addEventListener('load', function() { + window.opener.postMessage({ type: 'LOADED' }, '*'); + }); + + window.addEventListener('message', function(e) { + if (SECURE_ORIGIN !== window.location.origin) + return; + + if (e.data.type === "initialize-iframe") + window.frames[0].location = e.data.url; + if (e.data.type === "navigate-iframe") + window.frames[0].postMessage({ type: 'navigate', url: e.data.url }, '*'); + + // Relay messages sent by the subframe to the opener. + if (e.data.type === 'FRAME_READY') + window.opener.postMessage({ type: 'FRAME_READY' }, '*'); + + if (e.data.type === 'FRAME_COOKIES_SET') + window.opener.postMessage({ type: 'FRAME_COOKIES_SET', cookies: e.data.cookies }, '*'); + }); +</script> +<iframe></iframe> diff --git a/tests/wpt/web-platform-tests/cookies/samesite/resources/navigate.html b/tests/wpt/web-platform-tests/cookies/samesite/resources/navigate.html index 7d0f87d4943..88de6dff92e 100644 --- a/tests/wpt/web-platform-tests/cookies/samesite/resources/navigate.html +++ b/tests/wpt/web-platform-tests/cookies/samesite/resources/navigate.html @@ -3,15 +3,13 @@ <script src="/cookies/resources/cookie-helper.sub.js"></script> <script> window.addEventListener('load', function() { - window.opener.postMessage({ type: 'READY' }, '*'); + if (window.opener) + window.opener.postMessage({ type: 'READY' }, '*'); + if (window.parent !== window) + window.parent.postMessage({ type: 'FRAME_READY' }, '*'); }); window.addEventListener('message', function(e) { - if (SECURE_ORIGIN !== window.location.origin) - return; - if (window.location.origin !== e.origin) - return; - if (e.data.type === "navigate") { window.location = e.data.url; } diff --git a/tests/wpt/web-platform-tests/cookies/samesite/setcookie-navigation.https.html b/tests/wpt/web-platform-tests/cookies/samesite/setcookie-navigation.https.html index 0c6140c4032..06f9a73a7b4 100644 --- a/tests/wpt/web-platform-tests/cookies/samesite/setcookie-navigation.https.html +++ b/tests/wpt/web-platform-tests/cookies/samesite/setcookie-navigation.https.html @@ -7,11 +7,13 @@ <script src="/resources/testharnessreport.js"></script> <script src="/cookies/resources/cookie-helper.sub.js"></script> <script> - function assert_samesite_cookies_present(cookies, value) { - let samesite_cookie_names = ["samesite_strict", "samesite_lax", "samesite_none", "samesite_unspecified"]; - for (name of samesite_cookie_names) { + // Asserts that cookies are present or not present (according to `expectation`) + // in the cookie string `cookies` with the correct names and value. + function assert_cookies_present(cookies, value, expected_cookie_names, expectation) { + for (name of expected_cookie_names) { let re = new RegExp("(?:^|; )" + name + "=" + value + "(?:$|;)"); - assert_true(re.test(cookies), "`" + name + "=" + value + "` in cookies"); + let assertion = expectation ? assert_true : assert_false; + assertion(re.test(cookies), "`" + name + "=" + value + "` in cookies"); } } @@ -32,7 +34,40 @@ let command = (method === "POST") ? "post-form" : "navigate"; w.postMessage({ type: command, url: url_to }, "*"); let message = await wait_for_message('COOKIES_SET', origin_to); - assert_samesite_cookies_present(message.data.cookies, value); + let samesite_cookie_names = ['samesite_strict', 'samesite_lax', 'samesite_none', 'samesite_unspecified']; + assert_cookies_present(message.data.cookies, value, samesite_cookie_names, true); + w.close(); + }, title); + } + + // Opens a page on origin SECURE_ORIGIN containing an iframe on `iframe_origin_from`, + // then navigates that iframe to `iframe_origin_to`. Expects that navigation to set + // some subset of SameSite cookies. + function navigate_iframe_test(iframe_origin_from, iframe_origin_to, cross_site, title) { + promise_test(async function(t) { + // The cookies don't need to be cleared on each run because |value| is + // a new random value on each run, so on each run we are overwriting and + // checking for a cookie with a different random value. + let value = "" + Math.random(); + let parent_url = SECURE_ORIGIN + "/cookies/samesite/resources/navigate-iframe.html"; + let iframe_url_from = iframe_origin_from + "/cookies/samesite/resources/navigate.html"; + let iframe_url_to = iframe_origin_to + "/cookies/resources/setSameSite.py?" + value; + var w = window.open(parent_url); + await wait_for_message('LOADED', SECURE_ORIGIN); + assert_equals(SECURE_ORIGIN, window.origin); + assert_equals(SECURE_ORIGIN, w.origin); + // Navigate the frame to its starting location. + w.postMessage({ type: 'initialize-iframe', url: iframe_url_from }, '*'); + await wait_for_message('FRAME_READY', SECURE_ORIGIN); + // Have the frame navigate itself, possibly cross-site. + w.postMessage({ type: 'navigate-iframe', url: iframe_url_to }, '*'); + let message = await wait_for_message('FRAME_COOKIES_SET', SECURE_ORIGIN); + // Check for the proper cookies. + let samesite_none_cookies = ['samesite_none']; + let samesite_cookies = ['samesite_strict', 'samesite_lax']; + (isLegacySameSite() ? samesite_none_cookies : samesite_cookies).push('samesite_unspecified'); + assert_cookies_present(message.data.cookies, value, samesite_none_cookies, true); + assert_cookies_present(message.data.cookies, value, samesite_cookies, !cross_site); w.close(); }, title); } @@ -41,4 +76,9 @@ navigate_test("GET", SECURE_CROSS_SITE_ORIGIN, "Cross-site top-level navigation should be able to set SameSite=* cookies."); navigate_test("POST", SECURE_ORIGIN, "Same-site top-level POST should be able to set SameSite=* cookies."); navigate_test("POST", SECURE_CROSS_SITE_ORIGIN, "Cross-site top-level POST should be able to set SameSite=* cookies."); + + navigate_iframe_test(SECURE_ORIGIN, SECURE_ORIGIN, false, "Same-site to same-site iframe navigation should be able to set SameSite=* cookies."); + navigate_iframe_test(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN, true, "Cross-site to same-site iframe navigation should only be able to set SameSite=None cookies."); + navigate_iframe_test(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, true, "Same-site to cross-site-site iframe navigation should only be able to set SameSite=None cookies."); + navigate_iframe_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, true, "Cross-site to cross-site iframe navigation should only be able to set SameSite=None cookies."); </script> diff --git a/tests/wpt/web-platform-tests/cors/resources/cors-headers.asis b/tests/wpt/web-platform-tests/cors/resources/cors-headers.asis index ce21245f124..2e92da28a0a 100644 --- a/tests/wpt/web-platform-tests/cors/resources/cors-headers.asis +++ b/tests/wpt/web-platform-tests/cors/resources/cors-headers.asis @@ -4,6 +4,7 @@ Access-Control-Expose-Headers: X-Custom-Header, X-Custom-Header-Empty, X-Custom- Access-Control-Expose-Headers: X-Second-Expose
Access-Control-Expose-Headers: Date
Content-Type: text/plain
+Content-Length: 4 X-Custom-Header: test
X-Custom-Header: test
Set-Cookie: test1=t1;max-age=2
diff --git a/tests/wpt/web-platform-tests/cors/response-headers.htm b/tests/wpt/web-platform-tests/cors/response-headers.htm index d4d7cf231fa..cc06a239a64 100644 --- a/tests/wpt/web-platform-tests/cors/response-headers.htm +++ b/tests/wpt/web-platform-tests/cors/response-headers.htm @@ -44,6 +44,8 @@ default_readable("Content-Language", "nn"); default_readable("Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); default_readable("Last-Modified", "Thu, 01 Dec 1994 10:00:00 GMT"); default_readable("Pragma", "no-cache"); +default_readable("Content-Length", "4"); +default_readable("Content-Type", "text/plain"); function default_unreadable(head) { diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-attachment-fixed-inside-transform-1-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-attachment-fixed-inside-transform-1-ref.html index 8d4c3f785c8..8d4c3f785c8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-attachment-fixed-inside-transform-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-attachment-fixed-inside-transform-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-attachment-fixed-inside-transform-1.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-attachment-fixed-inside-transform-1.html index 9dae31aaccd..9dae31aaccd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-attachment-fixed-inside-transform-1.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-attachment-fixed-inside-transform-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1-ref.html index ecd58d762f9..ecd58d762f9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1a.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1a.html index 6717ce1b348..6717ce1b348 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1a.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1a.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1b.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1b.html index b3e1e9684b8..b3e1e9684b8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1b.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1b.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1c.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1c.html index 3fcfd80b1c7..3fcfd80b1c7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1c.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1d.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1d.html index 69045cf605a..69045cf605a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1d.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1d.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1e.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1e.html index 5fd4973dcd8..5fd4973dcd8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-1e.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-1e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-2-ref.html index b530e61a6b5..b530e61a6b5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-2.html index 702931abe84..702931abe84 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-2.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-2.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-3-ref.html index e44cecd8f5c..e44cecd8f5c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-3-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-3.html index c3b5e2b2d00..c3b5e2b2d00 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-3.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-3.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-4-ref.html index 8139ace8dad..8139ace8dad 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-4-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-4.html index be1a4c8ae9c..be1a4c8ae9c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-round-4.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-round-4.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1-ref.html index 11ea2c7bc1e..11ea2c7bc1e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-10-ref.html index f4c2b7c18e2..f4c2b7c18e2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-10-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-10.html index 00205a938b8..00205a938b8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-10.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-10.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1a.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1a.html index e4376c6391d..e4376c6391d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1a.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1a.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1b.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1b.html index 55bc6dc704d..55bc6dc704d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1b.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1b.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1c.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1c.html index 27470f9e0c0..27470f9e0c0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-1c.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-1c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-2-ref.html index 5dbd30d550a..5dbd30d550a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-2.html index ac9664482c5..ac9664482c5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-2.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-2.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-3-ref.html index f132f2eceac..f132f2eceac 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-3-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-3.html index 9ea709b81f3..9ea709b81f3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-3.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-3.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-4-ref.html index a375db50cd0..a375db50cd0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-4-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-4.html index 834381c76fe..834381c76fe 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-4.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-4.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-5-ref.html index d9d3ca11842..d9d3ca11842 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-5-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-5.html index 0beca23d964..0beca23d964 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-5.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-5.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-6-ref.html index c07440184b6..c07440184b6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-6-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-6.html index ae2c0fa8f63..ae2c0fa8f63 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-6.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-6.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-7-ref.html index 541fbd4ddc2..541fbd4ddc2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-7-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-7.html index 809875f57be..809875f57be 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-7.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-7.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-8-ref.html index 58464091713..58464091713 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-8-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-8.html index 473d115ba69..473d115ba69 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-8.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-8.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-9-ref.html index 6aa9c732155..6aa9c732155 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-9-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-9.html index 28ff46473be..28ff46473be 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/background-repeat-space-9.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-repeat-space-9.html diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-041.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-041.html new file mode 100644 index 00000000000..6eb8e59eeac --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-041.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Backgrounds and Borders Test: background-size with <length> value and 'auto' and image with no intrinsic size and no intrinsic ratio</title> + + <!-- + Credit should go to Henrik Andersson + for originally reporting this issue. + --> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="http://www.w3.org/TR/css3-background/#the-background-size"> + + <!-- + + [css-backgrounds] [css-images] Interop for sizing gradients with only width + http://lists.w3.org/Archives/Public/www-style/2017Apr/0029.html + + Issue 935057: background-size with only one [ <length | <percent> ] value with SVG image with no intrinsic ratio incorrect + https://bugs.chromium.org/p/chromium/issues/detail?id=935057 + + Bug 170834: background-size with 2nd value is 'auto' and the image has no intrinsic size and no intrinsic ratio incorrectly rendered + https://bugs.webkit.org/show_bug.cgi?id=170834 + + --> + + <link rel="match" href="reference/background-size-041-ref.html"> + + <meta content="" name="flags"> + <meta content="This test checks that when one 'background-size' is 'auto' and the image has no intrinsic size and no intrinsic ratio, then it should use 100%. In this test, the 'auto' value should use 100% of the height of the background positioning area of the div, which is 400px." name="assert"> + + <style> + div + { + background-clip: border-box; + background-image: linear-gradient(orange, blue); + background-origin: content-box; + background-position: center; + background-repeat: no-repeat; + background-size: 200px auto; + border: solid 40px; + border-color: transparent black; + height: 400px; + width: 400px; + } + </style> + + <div></div> diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-042.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-042.html new file mode 100644 index 00000000000..383bc5333d6 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-042.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Backgrounds and Borders Test: background-size with <percent> value and 'auto' and image with no intrinsic size and no intrinsic ratio</title> + + <!-- + Credit should go to Henrik Andersson + for originally reporting this issue. + --> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="http://www.w3.org/TR/css3-background/#the-background-size"> + + <!-- + + [css-backgrounds] [css-images] Interop for sizing gradients with only width + http://lists.w3.org/Archives/Public/www-style/2017Apr/0029.html + + Issue 935057: background-size with only one [ <length | <percent> ] value with SVG image with no intrinsic ratio incorrect + https://bugs.chromium.org/p/chromium/issues/detail?id=935057 + + Bug 170834: background-size with 2nd value is 'auto' and the image has no intrinsic size and no intrinsic ratio incorrectly rendered + https://bugs.webkit.org/show_bug.cgi?id=170834 + + --> + + <link rel="match" href="reference/background-size-041-ref.html"> + + <meta content="" name="flags"> + <meta content="This test checks that when one 'background-size' is 'auto' and the image has no intrinsic size and no intrinsic ratio, then it should use 100%. In this test, the 'auto' value should use 100% of the height of the background positioning area of the div, which is 400px." name="assert"> + + <style> + div + { + background-clip: border-box; + background-image: linear-gradient(orange, blue); + background-origin: content-box; + background-position: center; + background-repeat: no-repeat; + background-size: 50% auto; + border: solid 40px; + border-color: transparent black; + height: 400px; + width: 400px; + } + </style> + + <div></div> diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-043.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-043.html new file mode 100644 index 00000000000..35b691aadfa --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-043.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Backgrounds and Borders Test: background-size with <length> value and 'auto' and image with no intrinsic size and no intrinsic ratio</title> + + <!-- + Credit should go to Henrik Andersson + for originally reporting this issue. + --> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="http://www.w3.org/TR/css3-background/#the-background-size"> + + <!-- + + [css-backgrounds] [css-images] Interop for sizing gradients with only width + http://lists.w3.org/Archives/Public/www-style/2017Apr/0029.html + + Issue 935057: background-size with only one [ <length | <percent> ] value with SVG image with no intrinsic ratio incorrect + https://bugs.chromium.org/p/chromium/issues/detail?id=935057 + + Bug 170834: background-size with 2nd value is 'auto' and the image has no intrinsic size and no intrinsic ratio incorrectly rendered + https://bugs.webkit.org/show_bug.cgi?id=170834 + + --> + + <link rel="match" href="reference/background-size-043-ref.html"> + + <meta content="" name="flags"> + <meta content="This test checks that when one 'background-size' is 'auto' and the image has no intrinsic size and no intrinsic ratio, then it should use 100%. In this test, the 'auto' value should use 100% of the height of the background positioning area of the div, which is 400px." name="assert"> + + <style> + div + { + background-clip: border-box; + background-image: url("support/orange-intrinsic-none.svg"); + background-origin: content-box; + background-position: center; + background-repeat: no-repeat; + background-size: 200px auto; + border: solid 40px; + border-color: transparent black; + height: 400px; + width: 400px; + } + </style> + + <div></div> diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-044.html b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-044.html new file mode 100644 index 00000000000..ac2ef2ab3ad --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/background-size-044.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Backgrounds and Borders Test: background-size with <percent> value and 'auto' and image with no intrinsic size and no intrinsic ratio</title> + + <!-- + Credit should go to Henrik Andersson + for originally reporting this issue. + --> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="http://www.w3.org/TR/css3-background/#the-background-size"> + + <!-- + + [css-backgrounds] [css-images] Interop for sizing gradients with only width + http://lists.w3.org/Archives/Public/www-style/2017Apr/0029.html + + Issue 935057: background-size with only one [ <length | <percent> ] value with SVG image with no intrinsic ratio incorrect + https://bugs.chromium.org/p/chromium/issues/detail?id=935057 + + Bug 170834: background-size with 2nd value is 'auto' and the image has no intrinsic size and no intrinsic ratio incorrectly rendered + https://bugs.webkit.org/show_bug.cgi?id=170834 + + --> + + <link rel="match" href="reference/background-size-043-ref.html"> + + <meta content="" name="flags"> + <meta content="This test checks that when one 'background-size' is 'auto' and the image has no intrinsic size and no intrinsic ratio, then it should use 100%. In this test, the 'auto' value should use 100% of the height of the background positioning area of the div, which is 400px." name="assert"> + + <style> + div + { + background-clip: border-box; + background-image: url("support/orange-intrinsic-none.svg"); + background-origin: content-box; + background-position: center; + background-repeat: no-repeat; + background-size: 50% auto; + border: solid 40px; + border-color: transparent black; + height: 400px; + width: 400px; + } + </style> + + <div></div> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-1-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-1-ref.html index 942f3eb8c56..942f3eb8c56 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-1.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-1.html index 6e2a43b89d6..6e2a43b89d6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-1.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-1-ref.html index 298b46dd128..298b46dd128 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-1.html index 832126ef3df..832126ef3df 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-1.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-2-ref.html index e76f7cee832..e76f7cee832 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-2.html index 4e08ae03665..4e08ae03665 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-round-2.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-round-2.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-1-ref.html index 7c6555a0ae4..7c6555a0ae4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-1.html index c186a3139fe..c186a3139fe 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-1.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-2-ref.html index 6afc0733880..6afc0733880 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-2.html index b38bc847342..b38bc847342 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-2.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-2.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-3-ref.html index 904b325ccdc..904b325ccdc 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-3-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-3.html index 085498f76a9..085498f76a9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-3.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-3.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-1.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-4-ref-1.html index ff7700e9af1..ff7700e9af1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-1.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-4-ref-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-2.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-4-ref-2.html index dda3de638a2..dda3de638a2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4-ref-2.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-4-ref-2.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-4.html index d82969d71c4..d82969d71c4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-4.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-4.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-1.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-5-ref-1.html index d52cab5bbe9..d52cab5bbe9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-1.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-5-ref-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-2.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-5-ref-2.html index bda8d7237f3..bda8d7237f3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5-ref-2.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-5-ref-2.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-5.html index db21548af86..db21548af86 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-5.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-5.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-6-ref.html index 307eda0ea52..307eda0ea52 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-6-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-6.html index 045cde139d7..045cde139d7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-6.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-6.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-7-ref.html index 44b07f7696a..44b07f7696a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-7-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7.html b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-7.html index 367657858ea..367657858ea 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/border-image-repeat-space-7.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/border-image-repeat-space-7.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/box-shadow-currentcolor-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/box-shadow-currentcolor-ref.html index 4bb4cbd4178..4bb4cbd4178 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/box-shadow-currentcolor-ref.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/box-shadow-currentcolor-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/box-shadow-currentcolor.html b/tests/wpt/web-platform-tests/css/css-backgrounds/box-shadow-currentcolor.html index cdb0b9d9fc2..cdb0b9d9fc2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/box-shadow-currentcolor.html +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/box-shadow-currentcolor.html diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/linear-gradient-calc-crash.html b/tests/wpt/web-platform-tests/css/css-backgrounds/linear-gradient-calc-crash.html new file mode 100644 index 00000000000..5ae6104a794 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/linear-gradient-calc-crash.html @@ -0,0 +1,4 @@ +<!doctype html> +<title>CSS Backgrounds and Borders Test: Chrome linear-gradient crash test with large percentage calc()</title> +<link rel="help" href="https://crbug.com/1174046"> +<div style="background-image: linear-gradient(to left, black, red calc(1e39% + 0px), green);">Should not crash</div> diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/reference/background-size-041-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/reference/background-size-041-ref.html new file mode 100644 index 00000000000..fe66052ebf2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/reference/background-size-041-ref.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> + + <title>CSS Reftest Reference</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + + <style> + div#black-walls + { + border: solid 40px; + border-color: transparent black; + height: 400px; + width: 400px; + } + + div#gradient + { + background-image: linear-gradient(orange, blue); + height: 100%; /* computes to 400px */ + margin: 0px auto; + /* the gradient block is horizontally centered inside div#black-walls */ + width: 200px; + } + </style> + + <div id="black-walls"><div id="gradient"></div></div> diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/reference/background-size-043-ref.html b/tests/wpt/web-platform-tests/css/css-backgrounds/reference/background-size-043-ref.html new file mode 100644 index 00000000000..bf60e2183d8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/reference/background-size-043-ref.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> + + <title>CSS Reftest Reference</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + + <style> + div#black-walls + { + border: solid 40px; + border-color: transparent black; + height: 400px; + width: 400px; + } + + div#inner-orange + { + background-color: orange; + border: blue solid 16px; + height: 368px; /* 400px - 2 * 16px == 368px */ + margin: 0px auto; + /* the orange block is horizontally centered inside div#black-walls */ + width: 168px; /* 200px - 2 * 16px == 168px */ + } + </style> + + <div id="black-walls"><div id="inner-orange"></div></div> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-32x32.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/aqua-yellow-32x32.png Binary files differindex 42f8a2100b2..42f8a2100b2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-32x32.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/aqua-yellow-32x32.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-37x37.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/aqua-yellow-37x37.png Binary files differindex 0289b039413..0289b039413 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/aqua-yellow-37x37.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/aqua-yellow-37x37.png diff --git a/tests/wpt/web-platform-tests/css/css-backgrounds/support/orange-intrinsic-none.svg b/tests/wpt/web-platform-tests/css/css-backgrounds/support/orange-intrinsic-none.svg new file mode 100644 index 00000000000..d63fb9ef70b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/orange-intrinsic-none.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" preserveAspectRatio="none"> + <rect width="100%" height="100%" fill="orange" stroke="blue" stroke-width="32" /> +</svg> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bl.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-bl.png Binary files differindex 6abbaf319f3..6abbaf319f3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bl.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-bl.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bo.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-bo.png Binary files differindex f72a67381c6..f72a67381c6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-bo.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-bo.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-br.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-br.png Binary files differindex 84e22afe2a1..84e22afe2a1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-br.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-br.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ct.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-ct.png Binary files differindex 71ac10f6110..71ac10f6110 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ct.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-ct.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-le.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-le.png Binary files differindex abe56ffad64..abe56ffad64 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-le.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-le.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ri.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-ri.png Binary files differindex 95d7db84234..95d7db84234 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-ri.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-ri.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tl.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-tl.png Binary files differindex 8a3516998ab..8a3516998ab 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tl.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-tl.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-to.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-to.png Binary files differindex d1b082c9ba2..d1b082c9ba2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-to.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-to.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tr.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-tr.png Binary files differindex cd9bb5a5a40..cd9bb5a5a40 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule-tr.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule-tr.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule.png b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule.png Binary files differindex 02c7d10e76b..02c7d10e76b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/reticule.png +++ b/tests/wpt/web-platform-tests/css/css-backgrounds/support/reticule.png diff --git a/tests/wpt/web-platform-tests/css/css-break/empty-multicol-at-scrollport-edge.html b/tests/wpt/web-platform-tests/css/css-break/empty-multicol-at-scrollport-edge.html new file mode 100644 index 00000000000..b8813ed9cda --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-break/empty-multicol-at-scrollport-edge.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://www.w3.org/TR/css-break-3/#breaking-rules"> +<meta name="assert" content="When flowing into fragmentainers, we require them to accept at least 1px of content in the block direction, in order to guarantee progress. But the block-size of the fragmentainer itself shouldn't be stretched to 1px, as that would lead to overflow contibution"> +<div id="container" style="width:100px; height:100px; overflow:auto;"> + <div style="height:100px;"></div> + <div style="columns:3;"><div></div></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + test(()=> { + assert_equals(container.scrollHeight, 100); + }, "empty multicol doesn't add layout overflow"); +</script> diff --git a/tests/wpt/web-platform-tests/css/css-break/no-room-for-line-in-first-fragmentainer-crash.html b/tests/wpt/web-platform-tests/css/css-break/no-room-for-line-in-first-fragmentainer-crash.html new file mode 100644 index 00000000000..bc7b9aaa5e6 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-break/no-room-for-line-in-first-fragmentainer-crash.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1175666"> +<div style="width:500px; columns:3; column-fill:auto; height:100px; line-height:100px;"> + <div style="padding-top:50px;"> + <canvas style="float:left; width:200px; height:100px;"></canvas> + <span style="display:inline-block; width:100px; height:10px;"></span> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-016.html b/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-016.html new file mode 100644 index 00000000000..26590bc9064 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-016.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title> + Nested fragmentation for out-of-flow positioned elements. +</title> +<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking"> +<link rel="match" href="../reference/ref-filled-green-100px-square.xht"> +<style> + .multicol { + column-count: 2; + column-fill: auto; + column-gap: 0px; + } + #outer { + height: 100px; + width: 300px; + } + #inner { + width: 100px; + background-color: red; + position: relative; + left: -150px; + } + .rel { + position: relative; + height: 200px; + } + .abs { + position: absolute; + height: 200px; + width: 50px; + top: 0px; + background-color: green; + } +</style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div class="multicol" id="outer"> + <div class="multicol" id="inner"> + <div style="height: 200px;"></div> + <div class="rel"> + <div class="abs"></div> + </div> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-017.html b/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-017.html new file mode 100644 index 00000000000..aa56a7cddfc --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-017.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title> + Out-of-flow static position in nested context. +</title> +<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking"> +<link rel="match" href="../reference/ref-filled-green-100px-square.xht"> +<style> + .multicol { + column-count: 2; + column-fill: auto; + column-gap: 0px; + } + #outer { + height: 100px; + width: 300px; + } + #inner { + width: 100px; + background-color: red; + position: relative; + left: -150px; + } + .rel { + position: relative; + height: 360px; + } + .abs { + position: absolute; + height: 180px; + width: 50px; + background-color: green; + } +</style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div class="multicol" id="outer"> + <div class="multicol" id="inner"> + <div class="rel"> + <div style="height: 180px;"></div> + <div style="column-span: all; height: 20px; background-color: green;"></div> + <div class="abs"></div> + </div> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-018.html b/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-018.html new file mode 100644 index 00000000000..8c42d66a055 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-break/out-of-flow-in-multicolumn-018.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title> + Nested fragmentation for out-of-flow positioned elements. +</title> +<link rel="help" href="https://www.w3.org/TR/css-position-3/#abspos-breaking"> +<link rel="match" href="../reference/ref-filled-green-100px-square.xht"> +<style> + .multicol { + column-count: 2; + column-fill: auto; + column-gap: 0px; + } + #outer { + height: 100px; + width: 100px; + } + #inner { + width: 50px; + background-color: red; + } + .rel { + position: relative; + height: 360px; + } + .abs { + position: absolute; + bottom: 0px; + top: 0px; + width: 25px; + background-color: green; + } +</style> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div class="multicol" id="outer"> + <div class="multicol" id="inner"> + <div class="rel"> + <div style="height: 180px;"></div> + <div style="column-span: all; height: 20px; background-color: green;"></div> + <div class="abs"></div> + </div> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-001-ref.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-001-ref.html index f37a73d1707..f37a73d1707 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-001.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-001.html index 2b8c7d313c7..2b8c7d313c7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-001.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-002-ref.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-002-ref.html index 90284b999f1..90284b999f1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-002.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-002.html index a1e9a0127e3..a1e9a0127e3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-002.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-003-ref.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-003-ref.html index e0db417c99c..e0db417c99c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-003.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-003.html index f9c256e1773..f9c256e1773 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-003.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-003.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-004-ref.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-004-ref.html index f630740fae1..f630740fae1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-004.html b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-004.html index 1cd6684c710..1cd6684c710 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-hsl-004.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-hsl-004.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-001-ref.html b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-001-ref.html index b03f0a3fb2a..b03f0a3fb2a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-001.html b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-001.html index 061eeac2fac..061eeac2fac 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-001.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-002-ref.html b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-002-ref.html index be29c86ca72..be29c86ca72 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-002.html b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-002.html index 8919f0ea3e4..8919f0ea3e4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-002.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-003-ref.html b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-003-ref.html index 70227ad3820..70227ad3820 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-003.html b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-003.html index 6a22ba0dbbb..6a22ba0dbbb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/background-color-rgb-003.html +++ b/tests/wpt/web-platform-tests/css/css-color/background-color-rgb-003.html diff --git a/tests/wpt/web-platform-tests/css/css-color/color-resolving-hwb.html b/tests/wpt/web-platform-tests/css/css-color/color-resolving-hwb.html new file mode 100644 index 00000000000..bfa0eb022fe --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/color-resolving-hwb.html @@ -0,0 +1,103 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: Resolving HWB color values</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#hwb-to-rgb"> +<meta name="assert" content="Tests if HWB color values are resolved properly"> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id="parent" style="color: rgb(45, 23, 27)"> + <div id="inner"></div> +</div> + +<script> + function color_test(color, expected, reason) { + test(function() { + var element = document.getElementById('inner'); + // Random value not in our test data. + fail_value = "rgb(12, 34, 223)" + element.style.color = "black"; + element.style.cssText = "color: " + fail_value + "; color: " + color; + + if (expected === null) + assert_equals(getComputedStyle(element).color, fail_value); + else + assert_equals(getComputedStyle(element).color, expected); + }, `${reason}: ${color}`); + } + + function expected_value(rgb_channels) { + if (rgb_channels === null) + return null; + else if (rgb_channels.length === 3 || rgb_channels[3] == 1 || rgb_channels[3] === undefined) + return "rgb(" + rgb_channels.slice(0, 3).join(", ") + ")"; + else + return "rgba(" + rgb_channels.join(", ") + ")"; + } + + function hslToRgb(hue, sat, light) { + if (light <= .5) { + var t2 = light * (sat + 1); + } else { + var t2 = light + sat - (light * sat); + } + var t1 = light * 2 - t2; + var r = hueToRgb(t1, t2, hue + 2); + var g = hueToRgb(t1, t2, hue); + var b = hueToRgb(t1, t2, hue - 2); + return [r,g,b]; + } + + function hueToRgb(t1, t2, hue) { + if (hue < 0) hue += 6; + if (hue >= 6) hue -= 6; + + if (hue < 1) return (t2 - t1) * hue + t1; + else if (hue < 3) return t2; + else if (hue < 4) return (t2 - t1) * (4 - hue) + t1; + else return t1; + } + + function hwbToRgb(hue, white, black) { + if (white + black >= 1) { + let gray = Math.min(Math.max(Math.round(white / (white + black) * 255), 0), 255); + return [gray, gray, gray]; + } + + var rgb = hslToRgb(hue, 1, .5); + for (var i = 0; i < 3; i++) { + rgb[i] *= (1 - white - black); + rgb[i] += white; + rgb[i] = Math.min(Math.max(Math.round(rgb[i] * 255), 0), 255); + } + return rgb; + } + + // Test HWB parsing. + for (var hue of [0, 30, 60, 90, 120, 180, 210, 240, 270, 300, 330, 360]) { + for (var white of [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]) { + for (var black of [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]) { + rgb_channels = hwbToRgb(hue / 60, white, black); + for (var alpha of [undefined, 0, 0.2, 1]) { + rgb_channels[3] = alpha; + + // Test hue using angle notation. + let hwb_color_using_degrees = `hwb(${hue}deg ${white * 100}% ${black * 100}%)`; + if (alpha !== undefined) { + hwb_color_using_degrees = `hwb(${hue}deg ${white * 100}% ${black * 100}% / ${alpha})`; + } + color_test(hwb_color_using_degrees, expected_value(rgb_channels), "HWB value should parse and round correctly"); + + // Test hue using number notation. + let hwb_color_using_number = `hwb(${hue} ${white * 100}% ${black * 100}%)`; + if (alpha !== undefined) { + hwb_color_using_number = `hwb(${hue} ${white * 100}% ${black * 100}% / ${alpha})`; + } + color_test(hwb_color_using_number, expected_value(rgb_channels), "HWB value should parse and round correctly"); + } + } + } + } +</script> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-001.html b/tests/wpt/web-platform-tests/css/css-color/hwb-001.html new file mode 100644 index 00000000000..d1dce30c8ef --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-001.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: hwb</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hwb-notation"> +<link rel="match" href="greensquare-ref.html"> +<meta name="assert" content="hwb() with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 12em; } + .test { background-color: hwb(120 0% 49.8039%); } /* green (sRGB #008000) converted to hwb */ +</style> +<body> + <p>Test passes if you see a green square, and no red.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-002.html b/tests/wpt/web-platform-tests/css/css-color/hwb-002.html new file mode 100644 index 00000000000..1850c999b50 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-002.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: hwb</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hwb-notation"> +<link rel="match" href="blacksquare-ref.html"> +<meta name="assert" content="hwb() with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 12em; } + .test { background-color: hwb(0 0% 100%); } /* black (sRGB #000000) converted to hwb */ +</style> +<body> + <p>Test passes if you see a black square, and no red.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-003-ref.html b/tests/wpt/web-platform-tests/css/css-color/hwb-003-ref.html new file mode 100644 index 00000000000..ae89347ea50 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-003-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: CSS Color 4: hwb</title> +<style> + .test { background-color: rgb(50% 50% 50%); width: 12em; height: 12em; } /* hwb(0 100% 100%) converted to sRGB */ +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-003.html b/tests/wpt/web-platform-tests/css/css-color/hwb-003.html new file mode 100644 index 00000000000..f56a68a5dce --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-003.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: hwb</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hwb-notation"> +<link rel="match" href="hwb-003-ref.html"> +<meta name="assert" content="hwb with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 6em; margin-top: 0; } + .ref { background-color: rgb(50% 50% 50%); width: 12em; height: 6em; margin-bottom: 0; } /* hwb(0 100% 100%) converted to sRGB */ + .test { background-color: hwb(0 100% 100%); } +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="ref"></div> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-004-ref.html b/tests/wpt/web-platform-tests/css/css-color/hwb-004-ref.html new file mode 100644 index 00000000000..ac0035accc4 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-004-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: CSS Color 4: hwb</title> +<style> + .test { background-color: rgb(20% 70% 20%); width: 12em; height: 12em; } /* hwb(120 20% 30%) converted to sRGB */ +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-004.html b/tests/wpt/web-platform-tests/css/css-color/hwb-004.html new file mode 100644 index 00000000000..31b1dc5ad52 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-004.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: hwb</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hwb-notation"> +<link rel="match" href="hwb-004-ref.html"> +<meta name="assert" content="hwb with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 6em; margin-top: 0; } + .ref { background-color: rgb(20% 70% 20%); width: 12em; height: 6em; margin-bottom: 0; } /* hwb(120 20% 30%) converted to sRGB */ + .test { background-color: hwb(120 20% 30%); } +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="ref"></div> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-005-ref.html b/tests/wpt/web-platform-tests/css/css-color/hwb-005-ref.html new file mode 100644 index 00000000000..1476e69d474 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-005-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: CSS Color 4: hwb</title> +<style> + .test { background-color: rgb(53.8462% 53.8462% 53.8462%); width: 12em; height: 12em; } /* hwb(120 70% 60%) converted to sRGB */ +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/hwb-005.html b/tests/wpt/web-platform-tests/css/css-color/hwb-005.html new file mode 100644 index 00000000000..e3603e9a901 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/hwb-005.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: hwb</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#the-hwb-notation"> +<link rel="match" href="hwb-005-ref.html"> +<meta name="assert" content="hwb with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 6em; margin-top: 0; } + .ref { background-color: rgb(53.8462% 53.8462% 53.8462%); width: 12em; height: 6em; margin-bottom: 0; } /* hwb(120 70% 60%) converted to sRGB */ + .test { background-color: hwb(120 70% 60%); } +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="ref"></div> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/xyz-001.html b/tests/wpt/web-platform-tests/css/css-color/xyz-001.html new file mode 100644 index 00000000000..7f39d410931 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/xyz-001.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: xyz</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#valdef-color-xyz"> +<link rel="match" href="greensquare-ref.html"> +<meta name="assert" content="xyz with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 12em; } + .test { background-color: color(xyz 0.08312 0.154746 0.020961); } /* green (sRGB #008000) converted to xyz */ +</style> +<body> + <p>Test passes if you see a green square, and no red.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/xyz-002.html b/tests/wpt/web-platform-tests/css/css-color/xyz-002.html new file mode 100644 index 00000000000..832a1948a2c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/xyz-002.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: xyz</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#valdef-color-xyz"> +<link rel="match" href="blacksquare-ref.html"> +<meta name="assert" content="xyz with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 12em; } + .test { background-color: color(xyz 0 0 0); } /* black (sRGB #000000) converted to xyz */ +</style> +<body> + <p>Test passes if you see a black square, and no red.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/xyz-003-ref.html b/tests/wpt/web-platform-tests/css/css-color/xyz-003-ref.html new file mode 100644 index 00000000000..5dff0c43443 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/xyz-003-ref.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: CSS Color 4: xyz</title> +<style> + body { background-color: grey; } + .test { background-color: lab(100% 6.1097 -13.2268); width: 12em; height: 12em; } /* color(xyz 1 1 1) converted to Lab */ +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/xyz-003.html b/tests/wpt/web-platform-tests/css/css-color/xyz-003.html new file mode 100644 index 00000000000..df9ac71da24 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/xyz-003.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: xyz</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#valdef-color-xyz"> +<link rel="match" href="xyz-003-ref.html"> +<meta name="assert" content="xyz with no alpha"> +<style> + body { background-color: grey; } + .test { background-color: red; width: 12em; height: 6em; margin-top: 0; } + .ref { background-color: lab(100% 6.1097 -13.2268); width: 12em; height: 6em; margin-bottom: 0; } /* color(xyz 1 1 1) converted to Lab */ + .test { background-color: color(xyz 1 1 1); } +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="ref"></div> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/xyz-004-ref.html b/tests/wpt/web-platform-tests/css/css-color/xyz-004-ref.html new file mode 100644 index 00000000000..d1afb7c115b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/xyz-004-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: CSS Color 4: xyz</title> +<style> + .test { background-color: lab(100% -431.0345 172.4138); width: 12em; height: 12em; } /* color(xyz 0 1 0) converted to Lab */ +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/xyz-004.html b/tests/wpt/web-platform-tests/css/css-color/xyz-004.html new file mode 100644 index 00000000000..76c2ded7124 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/xyz-004.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: xyz</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#valdef-color-xyz"> +<link rel="match" href="xyz-004-ref.html"> +<meta name="assert" content="xyz with no alpha"> +<style> + .test { background-color: red; width: 12em; height: 6em; margin-top: 0; } + .ref { background-color: lab(100% -431.0345 172.4138); width: 12em; height: 6em; margin-bottom: 0; } /* color(xyz 0 1 0) converted to Lab */ + .test { background-color: color(xyz 0 1 0); } +</style> +<body> + <p>Test passes if you see a single square, and not two rectangles of different colors.</p> + <div class="ref"></div> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-color/xyz-005.html b/tests/wpt/web-platform-tests/css/css-color/xyz-005.html new file mode 100644 index 00000000000..a1c068754b6 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-color/xyz-005.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Color 4: xyz</title> +<link rel="author" title="Sam Weinig" href="mailto:weinig@apple.com"> +<link rel="help" href="https://drafts.csswg.org/css-color-4/#valdef-color-xyz"> +<link rel="match" href="greensquare-display-p3-ref.html"> +<meta name="assert" content="xyz outside the sRGB gamut"> +<style> + .test { background-color: red; width: 12em; height: 12em; } + .test { background-color: color(xyz 0.29194 0.692236 0.041884); } /* green color(display-p3 0 1 0) converted to xyz */ +</style> +<body> + <p>Test passes if you see a green square, and no red.</p> + <div class="test"></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-containing-block-absolute-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-containing-block-absolute-001.html index 036cf7d8dd6..036cf7d8dd6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-containing-block-absolute-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-containing-block-absolute-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-containing-block-fixed-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-containing-block-fixed-001.html index 979d71d406d..979d71d406d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-containing-block-fixed-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-containing-block-fixed-001.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-float-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-float-001.html new file mode 100644 index 00000000000..3486f639998 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-float-001.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: layout' should contain floats as a formatting context.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-layout"> + <link rel="match" href="contain-paint-formatting-context-float-001-ref.html"> + <style> + #left { + float: left; + height: 50px; + width: 10px; + background: blue; + } + #a { + contain: layout; + background: red; + margin: 10px; + width: 50px; + height: 50px; + } + #b { + clear: left; + width: 50px; + height: 50px; + background: green; + } + </style> +</head> +<body> + <div id="left"></div> + <div id="a"> + <div id="b"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-margin-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-margin-001-ref.html index 5a6d6538624..5a6d6538624 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-margin-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-margin-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-margin-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-margin-001.html new file mode 100644 index 00000000000..3b8f9caa751 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-formatting-context-margin-001.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: layout' with a vertical margin child. Margin collapse should not occur, and neither should overflow clipping.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-layout"> + <link rel="match" href="contain-layout-formatting-context-margin-001-ref.html"> + <style> + #a { + contain:layout; + background: blue; + margin: 10px; + width: 50px; + height: 50px; + } + #b { + width: 50px; + height: 40px; + background: green; + margin-top: 10px; + } + #c { + background: lightblue; + width: 50px; + height: 10px; + } + </style> +</head> +<body> + <div id="a"> + <div id="b"></div> + <div id="c"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-ib-split-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-ib-split-001-ref.html index eb787424ed4..eb787424ed4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-ib-split-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-ib-split-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-ib-split-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-ib-split-001.html index d9f976deb8c..d9f976deb8c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-ib-split-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-ib-split-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-001.html index bd2f4cb8178..bd2f4cb8178 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-002-ref.html index 44cd7c109e0..44cd7c109e0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-002.html index de2edfb58f8..de2edfb58f8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-002.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-003-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-003-ref.html index 46f028c0581..46f028c0581 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-003.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-003.html index d40a0211566..d40a0211566 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-ignored-cases-no-principal-box-003.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-ignored-cases-no-principal-box-003.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-001-ref.html index 61825fd4541..61825fd4541 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-001.html index 5bf984e2bf1..5bf984e2bf1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-002-ref.html index ba1c600d504..ba1c600d504 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-002.html index 4929dc5d33e..4929dc5d33e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-overflow-002.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-overflow-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-stacking-context-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-stacking-context-001.html index 4ec3bcee6fd..4ec3bcee6fd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-stacking-context-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-stacking-context-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-001-ref.html index e1a478acd69..e1a478acd69 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-001.html index b023cfdf96d..b023cfdf96d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-002-ref.html index 0587c90c35e..0587c90c35e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-002.html index a370386edee..a370386edee 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-suppress-baseline-002.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-layout-suppress-baseline-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-001-ref.html index 629cb939e01..629cb939e01 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-001.html new file mode 100644 index 00000000000..6637e08393b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-001.html @@ -0,0 +1,63 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' with various overflowing block descendants.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-clip-001-ref.html"> + <style> + .root { + contain: paint; + width: 100px; + height: 100px; + background: blue; + margin: 25px; + padding: 25px; + } + .a { + width: 100px; + height: 200px; + background: red; + } + .b { + width: 150px; + height: 150px; + background: green; + position: relative; + top: -25px; + left: -25px; + } + .background { + position: absolute; + top: 0; + left: 0; + width: 200px; + height: 200px; + background: red; + z-index: -1; + } + .foreground { + position: absolute; + top: -25px; + left: -25px; + width: 150px; + height: 150px; + border: 25px solid red; + z-index: 1; + } + </style> +</head> +<body> + <div class="root"> + <div class="a"> + <div class="b"></div> + <!--These two absolutely positioned elements are checking that all sides are--> + <!--clipped. They also test that clipping is done correctly on absolutely--> + <!--positioned elements.--> + <div class="background"></div> + <div class="foreground"></div> + </div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-002-ref.html index 3baefff57cf..3baefff57cf 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-002.html new file mode 100644 index 00000000000..ba16325b973 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-002.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' with overflowing text contents inside a rounded rectangle box.</title> + <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-clip-002-ref.html"> + <style> + .root { + contain: paint; + width: 100px; + height: 100px; + background: green; + margin: 25px; + padding: 10px; + border-radius: 4em; + } + </style> +</head> +<body> + <div class="root"> + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should + be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed + nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. + Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. + Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora + torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales + ligula in libero. + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-003-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-003-ref.html index 425844ae476..425844ae476 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-003.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-003.html new file mode 100644 index 00000000000..30a7335f152 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-003.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' with overflowing text contents, and 'overflow-y: scroll'.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-clip-003-ref.html"> + <style> + .root { + contain: paint; + overflow-y: scroll; + width: 100px; + height: 100px; + background: green; + margin: 25px; + padding: 25px; + } + </style> +</head> +<body> + <div class="root"> + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should + be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed + nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. + Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. + Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora + torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales + ligula in libero. + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-004-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-004-ref.html index 0861c68cbe3..0861c68cbe3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-004.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-004.html new file mode 100644 index 00000000000..709f191e85a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-004.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' with overflowing text contents, and 'overflow-x: scroll'.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-clip-004-ref.html"> + <style> + .root { + contain: paint; + overflow-x: scroll; + width: 100px; + height: 100px; + background: green; + margin: 25px; + padding: 25px; + } + </style> +</head> +<body> + <div class="root"> + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should + be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed + nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. + Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. + Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora + torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales + ligula in libero. + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-005.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-005.html new file mode 100644 index 00000000000..ff6db1a7307 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-005.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' on li with overflowing text contents and + bullet, and 'overflow-y: scroll'.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-clip-003-ref.html"> + <style> + ul { + padding: 0; + margin: 0; + } + .root { + contain: paint; + overflow-y: scroll; + width: 100px; + height: 100px; + background: green; + margin: 25px; + padding: 25px; + } + </style> +</head> +<body> + <ul> + <li class="root"> + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should + be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. + Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis + ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris + massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu + ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur + sodales ligula in libero. + </li> + </ul> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-006-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-006-ref.html index ecd22b307a9..ecd22b307a9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-006-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-006-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-006.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-006.html new file mode 100644 index 00000000000..a4d6835a165 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-clip-006.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' with overflowing text contents while "overflow-clip-box: content-box" enabled.</title> + <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-clip-006-ref.html"> + <style> + .root { + contain: paint; + width: 100px; + height: 100px; + background: green; + margin: 25px; + padding: 25px; + overflow-clip-box: content-box; + } + </style> +</head> +<body> + <div class="root"> + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should + be clipped to the content box. Lorem ipsum dolor sit amet, consectetur adipiscing + elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed + nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. + Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. + Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora + torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales + ligula in libero. + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-absolute-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-absolute-001-ref.html index 8861d19f888..8861d19f888 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-absolute-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-absolute-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-absolute-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-absolute-001.html new file mode 100644 index 00000000000..dd46c313db8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-absolute-001.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' element should contain absolute position elements.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-containing-block-absolute-001-ref.html"> + <style> + #a { + contain: paint; + width: 100px; + height: 100px; + background: red; + margin: 50px; + } + #b { + position: absolute; + top: 0; + left: 0; + width: 100px; + height: 100px; + background: green; + } + </style> +</head> +<body> + <div id="a"> + <div> + <div id="b"></div> + </div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-fixed-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-fixed-001-ref.html index 8861d19f888..8861d19f888 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-fixed-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-fixed-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-fixed-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-fixed-001.html new file mode 100644 index 00000000000..6fa52a5eb42 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-containing-block-fixed-001.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' element should contain fixed position elements.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-containing-block-fixed-001-ref.html"> + <style> + #a { + contain: paint; + width: 100px; + height: 100px; + background: red; + margin: 50px; + } + #b { + position: fixed; + top: 0; + left: 0; + width: 100px; + height: 100px; + background: green; + } + </style> +</head> +<body> + <div id="a"> + <div> + <div id="b"></div> + </div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-float-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-float-001-ref.html index ceff7f4569e..ceff7f4569e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-float-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-float-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-float-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-float-001.html new file mode 100644 index 00000000000..95242072b10 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-float-001.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' should contain floats as a formatting context.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-formatting-context-float-001-ref.html"> + <style> + #left { + float: left; + height: 50px; + width: 10px; + background: blue; + } + #a { + contain: paint; + background: red; + margin: 10px; + width: 50px; + height: 50px; + } + #b { + clear: left; + width: 50px; + height: 50px; + background: green; + } + </style> +</head> +<body> + <div id="left"></div> + <div id="a"> + <div id="b"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-margin-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-margin-001-ref.html index 1976f71bf55..1976f71bf55 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-margin-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-margin-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-margin-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-margin-001.html new file mode 100644 index 00000000000..ebf88866a94 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-formatting-context-margin-001.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: paint' with a vertical margin child. Margin collapse should not occur.</title> + <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-formatting-context-margin-001-ref.html"> + <style> + #a { + contain: paint; + background: blue; + margin: 10px; + width: 50px; + height: 50px; + } + #b { + width: 50px; + height: 40px; + background: green; + margin-top: 10px; + } + #c { + background: red; + width: 50px; + height: 10px; + } + </style> +</head> +<body> + <div id="a"> + <div id="b"></div> + <div id="c"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ib-split-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ib-split-001-ref.html index 8a698b9d2ce..8a698b9d2ce 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ib-split-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ib-split-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ib-split-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ib-split-001.html index eb21a2d4609..eb21a2d4609 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ib-split-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ib-split-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001-ref.html index d23678941e2..d23678941e2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001a.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001a.html new file mode 100644 index 00000000000..6e3bdbd2736 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001a.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset=utf-8> + <title>CSS-contain test: paint containment on internal table elements except table-cell.</title> + <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html"> + <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tr element, and confirms contain:paint does not create a stacking context."> + <style> + tr { + contain: paint; + z-index: 10; + } + th { + background-color: blue; + padding-left: 50px; + } + caption { + position: fixed; + background-color: yellow; + z-index: 2; + } + </style> + </head> + <body> + <table> + <caption>PASS</caption> + <tr> + <th> </th> + </tr> + </table> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001b.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001b.html new file mode 100644 index 00000000000..e531eb6ca39 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-internal-table-001b.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset=utf-8> + <title>CSS-contain test: paint containment on internal table elements except table-cell.</title> + <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html"> + <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tbody element, and confirms contain:paint does not create a stacking context."> + <style> + tbody { + contain: paint; + z-index: 10; + } + th { + background-color: blue; + padding-left: 50px; + } + caption { + position: fixed; + background-color: yellow; + z-index: 2; + } + </style> + </head> + <body> + <table> + <caption>PASS</caption> + <tbody> + <tr> + <th> </th> + </tr> + </tbody> + </table> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-no-principal-box-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-no-principal-box-001-ref.html index b4513709e51..b4513709e51 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-no-principal-box-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-no-principal-box-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-no-principal-box-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-no-principal-box-001.html index 4b9c9072099..4b9c9072099 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-no-principal-box-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-no-principal-box-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-containing-block-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-containing-block-001-ref.html index 5df0b246292..5df0b246292 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-containing-block-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-containing-block-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-containing-block-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-containing-block-001.html new file mode 100644 index 00000000000..08fa9555a6e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-containing-block-001.html @@ -0,0 +1,41 @@ +<!doctype html> +<html lang=en> + <head> + <meta charset=utf-8> + <title>CSS-contain test: paint containment on internal ruby elements.</title> + <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-ignored-cases-ruby-containing-block-001-ref.html"> + <meta name="assert" content="Paint containment should not apply to ruby base, ruby base container, ruby text, and ruby text container. This test confirms contain:paint does not act as a containing block for fixed positioned descendants."> + <style> + rb, + rbc, + rt, + rtc { + contain: paint; + background-color: yellow; + font-size: 2em; + } + rbc { + display: ruby-base-container; + } + .contained { + width: 50px; + height: 10px; + background-color: blue; + top: 0; + left: 0; + position: fixed; + } + .wrapper { + display: inline-block; + } + </style> + </head> + <body> + <div class="wrapper"><ruby><rt> <div class="contained"></div></rt></ruby></div> + <div class="wrapper"><ruby><rtc> <div class="contained"></div></rtc></ruby></div> + <div class="wrapper"><ruby><rb> <div class="contained"></div></rb></ruby></div> + <div class="wrapper"><ruby><rbc> <div class="contained"></div></rbc></ruby></div> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html index 02f7a406656..02f7a406656 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html new file mode 100644 index 00000000000..756035518df --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html @@ -0,0 +1,60 @@ +<!doctype html> +<html lang=en> + <head> + <meta charset=utf-8> + <title>CSS-contain test: paint containment on internal ruby elements.</title> + <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-paint"> + <link rel="match" href="contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html"> + <meta name="assert" content="Paint containment should not apply to ruby base, ruby base container, ruby text, and ruby text container. This test confirms that contain:paint does not create a stacking context and does not apply overflow clipping."> + <style> + div { + position: relative; + } + rb, + rbc, + rt, + rtc { + contain: paint; + } + rbc { + display: ruby-base-container; + } + .contained { + z-index: 5; + width: 70px; + height: 10px; + background-color: blue; + margin-left: -25px; + } + .background { + background-color: yellow; + height: 50px; + width: 50px; + position: fixed; + z-index: 2; + } + .group { + display: inline-block; + } + </style> + </head> + <body> + <div class="group"> + <div class="background"></div> + <ruby><rb> <div class="contained"></div></rb></ruby> + </div> + <div class="group"> + <div class="background"></div> + <ruby><rbc> <div class="contained"></div></rbc></ruby> + </div> + <div class="group"> + <div class="background"></div> + <ruby><rt> <div class="contained"></div></rt></ruby> + </div> + <div class="group"> + <div class="background"></div> + <ruby><rtc> <div class="contained"></div></rtc></ruby> + </div> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-stacking-context-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-stacking-context-001-ref.html index c7553716ab6..c7553716ab6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-stacking-context-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-stacking-context-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-stacking-context-001a.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-stacking-context-001a.html index 71102b6c73a..71102b6c73a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-stacking-context-001a.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-stacking-context-001a.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-stacking-context-001b.html b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-stacking-context-001b.html index 0c4d3323bf7..0c4d3323bf7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-stacking-context-001b.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-paint-stacking-context-001b.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-001-ref.html index 7f13a517a66..7f13a517a66 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-001.html index 0ed82ff8fc5..0ed82ff8fc5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-002-ref.html index 1f41028faac..1f41028faac 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-002.html index 4dc92103eab..4dc92103eab 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-002.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-003-ref.html index 2f6ff8322d1..2f6ff8322d1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-003.html index cf7e2310169..cf7e2310169 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-003.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-003.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-004-ref.html index c8647b19dd6..c8647b19dd6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-004.html index b6cca54a8c6..b6cca54a8c6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-block-004.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-block-004.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-button-002-ref.html index efadf9b5df4..efadf9b5df4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-button-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-size-button-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-button-002.html new file mode 100644 index 00000000000..9a2df76ff1a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-button-002.html @@ -0,0 +1,108 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: size' on buttons should cause them to be sized as if they had no contents.</title> + <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> + <link rel="match" href="contain-size-button-002-ref.html"> + <style> + button { + contain: size; + border: 1em solid green; + /* In case the testcase's 'inner' text is taller than the button, don't let + it influence its line-box's size. This lets us more-easily compare + sizing between empty buttons vs. contained nonempty buttons. */ + vertical-align: top; + } + .vaBaseline { + vertical-align: baseline; + } + .innerContents { + color: transparent; + height: 100px; + width: 100px; + } + .minWidth { + min-width: 50px; + } + .width { + width: 50px; + } + .minHeight { + min-height: 50px; + background: lightblue; + } + .height { + height: 50px; + background: lightblue; + } + .floatLBasic { + float: left; + } + .floatLWidth { + float: left; + width: 50px; + } + br { clear: both } + .iFlexBasic { + display: inline-flex; + } + .iFlexWidth { + display: inline-flex; + width: 50px; + } + .orthog { + writing-mode: vertical-lr; + } + </style> +</head> +<body> + <!--CSS Test: A size-contained button with no specified size should render at 0 height regardless of content.--> + <button><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained floated button with no specified size should render at 0px by 0px regardless of content.--> + <button class="floatLBasic"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained floated button with specified width and no specified height should render at given width and 0 height regardless of content.--> + <button class="floatLWidth"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained inline-flex button with no specified size should render at 0px by 0px regardless of content.--> + <button class="iFlexBasic"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained inline-flex button with specified width and no specified height should render at given width and 0 height regardless of content.--> + <button class="iFlexWidth"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained button should perform baseline alignment regularly.--> + outside before<button class="vaBaseline"><div class="innerContents">inner</div></button>outside after + <br> + + <!--CSS Test: A size-contained button with specified min-width should render at given min-width and zero height regardless of content.--> + <button class="minWidth"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained button with specified width should render at given width and zero height regardless of content.--> + <button class="width"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained button with specified min-height should render at given min-height regardless of content.--> + <button class="minHeight"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained button with specified height should render at given height regardless of content.--> + <button class="height"><div class="innerContents">inner</div></button> + <br> + + <!--CSS Test: A size-contained button with vertical text should perform baseline alignment regularly.--> + s<button class="orthog vaBaseline"><div class="innerContents">inner</div></button>endtext + <br> + + <!--CSS Test: A size-contained button with inner text should layout the text in the same manner as a container of the same type with identical contents.--> + <button class="height width">inside</button> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-003-ref.html index 6a77ee68879..6a77ee68879 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-003.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-003.html new file mode 100644 index 00000000000..e618d44f6dd --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-003.html @@ -0,0 +1,99 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: size' on fieldset elements should cause them to be sized as if they had no contents.</title> + <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> + <link rel="match" href="contain-size-fieldset-003-ref.html"> + <style> + .contain { + contain: size; + visibility: hidden; + border: none; + color: transparent; + } + .container { + border: 10px solid green; + display: inline-block; + vertical-align: top; + } + .innerContents { + height: 50px; + width: 50px; + } + .minHeight { + min-height: 30px; + } + .height { + height: 30px; + } + .minWidth { + min-width: 30px; + } + .width { + width: 30px; + } + </style> +</head> +<body> + <!--Note: The following .container class is used to help test if size-contained + fieldsets and non-contained fieldsets have the same size. Normally, we'd test + that a fieldset with children and size-containment is drawn identically to a + fieldset without containment or children. However, when we have a legend as + a child, border placement and padding of the fieldset are changed. + To check the dimensions between the ref-case and test-case without + failing because of the border/padding differences, we make the fieldset + {visibility:hidden; border:none;} and add a .container wrapper div.--> + + <!--CSS Test: A size-contained fieldset element with no specified size should size itself as if it had no contents.--> + <div class="container"> + <fieldset class="contain"> + <legend>legend</legend> + <div class="innerContents">inner</div> + </fieldset> + </div> + <br> + + <!--CSS Test: A size-contained fieldset element with specified min-height should size itself as if it had no contents.--> + <div class="container"> + <fieldset class="contain minHeight"> + <legend>legend</legend> + <div class="innerContents">inner</div> + </fieldset> + </div> + <br> + + <!--CSS Test: A size-contained fieldset element with specified height should size itself as if it had no contents.--> + <div class="container"> + <fieldset class="contain height"> + <legend>legend</legend> + <div class="innerContents">inner</div> + </fieldset> + </div> + <br> + + <!--CSS Test: A size-contained fieldset element with specified min-width should size itself as if it had no contents.--> + <div class="container"> + <fieldset class="contain minWidth"> + <legend>legend</legend> + <div class="innerContents">inner</div> + </fieldset> + </div> + <br> + + <!--CSS Test: A size-contained fieldset element with specified width should size itself as if it had no contents.--> + <div class="container"> + <fieldset class="contain width"> + <legend>legend</legend> + <div class="innerContents">inner</div> + </fieldset> + </div> + <br> + + <!--CSS Test: A size contained fieldset element with a legend should draw its legend and border in the same way as a non-contained fieldset element--> + <fieldset class="height" style="contain:size;"> + <legend>legend</legend> + </fieldset> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-004-ref.html index 198e8e12acc..198e8e12acc 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-004.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-004.html new file mode 100644 index 00000000000..2ea5d2583bb --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-fieldset-004.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: size' on fieldset elements shouldn't prevent them from being baseline-aligned regularly.</title> + <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> + <link rel="match" href="contain-size-fieldset-004-ref.html"> + <style> + .contain { + contain: size; + border: none; + color: transparent; + visibility: hidden; + } + .innerContents { + height: 50px; + width: 50px; + } + .flexBaselineCheck { + display: flex; + align-items: baseline; + } + </style> +</head> +<body> + <!--CSS Test: A size-contained fieldset element should perform baseline alignment regularly.--> + <div class="flexBaselineCheck"> + outside before<fieldset class="contain"> + <legend>legend</legend> + <div class="innerContents">inner</div> + </fieldset>outside after + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-flex-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-flex-001-ref.html index 995c45197fd..995c45197fd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-flex-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-flex-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-flex-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-flex-001.html index 72ab97043b7..72ab97043b7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-flex-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-flex-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-grid-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-grid-005-ref.html index b2fdf4b8876..b2fdf4b8876 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-grid-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-grid-005-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-size-grid-005.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-grid-005.html new file mode 100644 index 00000000000..4e4b681a731 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-grid-005.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Test: 'contain: size' on grid elements should cause them to be sized as if they had no contents.</title> + <link rel="author" title="Gerald Squelart" href="mailto:gsquelart@mozilla.com"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> + <link rel="match" href="contain-size-grid-005-ref.html"> + <style> + .contain { + display: grid; + contain:size; + border: 1em solid green; + background: red; + } + .innerContents { + color: transparent; + height: 100px; + width: 100px; + } + .minHeight { + min-height: 40px; + background: lightblue; + } + .height { + height: 40px; + background: lightblue; + } + .maxWidth { + max-width: 40px; + } + .width { + width: 40px; + } + .floatLBasic { + float: left; + } + .floatLWidth { + float: left; + width: 40px; + } + </style> +</head> +<body> + <!--CSS Test: A size-contained grid element with no specified size should render at 0 height regardless of content.--> + <div class="contain"><div class="innerContents">inner</div></div> + <br> + + <!--CSS Test: A size-contained grid element with specified min-height should render at given min-height regardless of content.--> + <div class="contain minHeight"><div class="innerContents">inner</div></div> + <br> + + <!--CSS Test: A size-contained grid element with specified height should render at given height regardless of content.--> + <div class="contain height"><div class="innerContents">inner</div></div> + <br> + + <!--CSS Test: A size-contained grid element with specified max-width should render at given max-width and zero height regardless of content.--> + <div class="contain maxWidth"><div class="innerContents">inner</div></div> + <br> + + <!--CSS Test: A size-contained grid element with specified width should render at given width and zero height regardless of content.--> + <div class="contain width"><div class="innerContents">inner</div></div> + <br> + + <!--CSS Test: A size-contained floated grid element with no specified size should render at 0px by 0px regardless of content.--> + <div class="contain floatLBasic"><div class="innerContents">inner</div></div> + <br> + + <!--CSS Test: A size-contained floated grid element with specified width and no specified height should render at given width and 0 height regardless of content.--> + <div class="contain floatLWidth"><div class="innerContents">inner</div></div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-001-ref.html index 92a6c7de5ee..92a6c7de5ee 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-001.html index 41458550272..41458550272 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-002-ref.html index 3d0f38f7c07..3d0f38f7c07 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-002.html index d3a69e5f357..d3a69e5f357 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-002.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-003-ref.html index 6079764591f..6079764591f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-003.html index 7ea22635d35..7ea22635d35 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-003.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-003.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-004-ref.html index e7f03ff2d31..e7f03ff2d31 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-004.html index 1de39348b71..1de39348b71 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-block-004.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-block-004.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-flex-001-ref.html index 93a263b5b2f..93a263b5b2f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-flex-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-flex-001.html index 15a2c943bb6..15a2c943bb6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-inline-flex-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-inline-flex-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-002-ref.html index c1c9f1388ed..c1c9f1388ed 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-002.html index 0e35e2fed26..0e35e2fed26 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-002.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-003-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-003-ref.html index af927aa268f..af927aa268f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-003.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-003.html index 447e3cc98dd..447e3cc98dd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-003.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-003.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-004-ref.html index 32dd4b6ab68..32dd4b6ab68 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-004.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-004.html new file mode 100644 index 00000000000..e63c213ab10 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-multicol-004.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> + <title>CSS Test: 'contain: size' should force elements to be monolithic, i.e. to not fragment inside a multicol element.</title> + <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> + <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> + <link rel="match" href="contain-size-multicol-004-ref.html"> + <style> + .contain { + contain:size; + } + .cols { + column-count: 3; + column-rule: 1px dotted blue; + column-fill: auto; + border: 2px solid blue; + height: 50px; + width: 300px; + } + .innerObject { + height: 200px; + width: 100px; + background: orange; + } + </style> +</head> + <body> + <div class="cols"> + <div class="contain innerObject"> + </div> + </div> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-001-ref.html index 095b2f0ba58..095b2f0ba58 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-001.html index fbb776a2568..fbb776a2568 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-002-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-002-ref.html index a2ec520bb3e..a2ec520bb3e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-002.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-002.html index c9fcfbd2cdf..c9fcfbd2cdf 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-002.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-003-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-003-ref.html index 214ad1891ba..214ad1891ba 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-003.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-003.html index 85428064ec3..85428064ec3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-003.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-003.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-004-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-004-ref.html index faf6228ea18..faf6228ea18 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-004.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-004.html index 818f4a30168..818f4a30168 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-004.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-004.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-005-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-005-ref.html index e14152d6338..e14152d6338 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-005-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-005-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-005.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-005.html index 127b3cd5f87..127b3cd5f87 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-select-elem-005.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-select-elem-005.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-table-caption-001-ref.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-table-caption-001-ref.html index 49b09334f2b..49b09334f2b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-table-caption-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-table-caption-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-table-caption-001.html b/tests/wpt/web-platform-tests/css/css-contain/contain-size-table-caption-001.html index 8ef46b76669..8ef46b76669 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-table-caption-001.html +++ b/tests/wpt/web-platform-tests/css/css-contain/contain-size-table-caption-001.html diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-101-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-101-ref.html new file mode 100644 index 00000000000..5c3d02d65d9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-101-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>arabic-indic, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: arabic-indic produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: arabic-indic; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class='test'><ol> +<div><bdi>١. </bdi>١</div> +<div><bdi>٢. </bdi>٢</div> +<div><bdi>٣. </bdi>٣</div> +<div><bdi>٤. </bdi>٤</div> +<div><bdi>٥. </bdi>٥</div> +<div><bdi>٦. </bdi>٦</div> +<div><bdi>٧. </bdi>٧</div> +<div><bdi>٨. </bdi>٨</div> +<div><bdi>٩. </bdi>٩</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-101.html b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-101.html index 623e3382368..21cd9be2289 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-101.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-101.html @@ -5,13 +5,13 @@ <title>arabic-indic, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-101-ref.html'> <meta name="assert" content="list-style: arabic-indic produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: arabic-indic; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-102-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-102-ref.html new file mode 100644 index 00000000000..610b68d3f01 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-102-ref.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>arabic-indic, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: arabic-indic produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: arabic-indic; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class='test'> +<ol start='10'> +<div><bdi>١٠. </bdi>١٠</div> +<div><bdi>١١. </bdi>١١</div> +<div><bdi>١٢. </bdi>١٢</div> +</ol> +<ol start='43'> +<div><bdi>٤٣. </bdi>٤٣</div> +</ol> +<ol start='77'> +<div><bdi>٧٧. </bdi>٧٧</div> +</ol> +<ol start='80'> +<div><bdi>٨٠. </bdi>٨٠</div> +</ol> +<ol start='99'> +<div><bdi>٩٩. </bdi>٩٩</div> +<div><bdi>١٠٠. </bdi>١٠٠</div> +<div><bdi>١٠١. </bdi>١٠١</div> +</ol> +<ol start='222'> +<div><bdi>٢٢٢. </bdi>٢٢٢</div> +</ol> +<ol start='540'> +<div><bdi>٥٤٠. </bdi>٥٤٠</div> +</ol> +<ol start='999'> +<div><bdi>٩٩٩. </bdi>٩٩٩</div> +<div><bdi>١٠٠٠. </bdi>١٠٠٠</div> +</ol> +<ol start='1005'> +<div><bdi>١٠٠٥. </bdi>١٠٠٥</div> +</ol> +<ol start='1060'> +<div><bdi>١٠٦٠. </bdi>١٠٦٠</div> +</ol> +<ol start='1065'> +<div><bdi>١٠٦٥. </bdi>١٠٦٥</div> +</ol> +<ol start='1800'> +<div><bdi>١٨٠٠. </bdi>١٨٠٠</div> +</ol> +<ol start='1860'> +<div><bdi>١٨٦٠. </bdi>١٨٦٠</div> +</ol> +<ol start='1865'> +<div><bdi>١٨٦٥. </bdi>١٨٦٥</div> +</ol> +<ol start='5865'> +<div><bdi>٥٨٦٥. </bdi>٥٨٦٥</div> +</ol> +<ol start='7005'> +<div><bdi>٧٠٠٥. </bdi>٧٠٠٥</div> +</ol> +<ol start='7800'> +<div><bdi>٧٨٠٠. </bdi>٧٨٠٠</div> +</ol> +<ol start='7864'> +<div><bdi>٧٨٦٤. </bdi>٧٨٦٤</div> +</ol> +<ol start='9999'> +<div><bdi>٩٩٩٩. </bdi>٩٩٩٩</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-102.html b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-102.html index 47ec8c20a11..5c86456c945 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-102.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-102.html @@ -5,13 +5,13 @@ <title>arabic-indic, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-102-ref.html'> <meta name="assert" content="list-style-type: arabic-indic produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: arabic-indic; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-103-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-103-ref.html new file mode 100644 index 00000000000..a71557cc28c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-103-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>arabic-indic, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: arabic-indic produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: arabic-indic; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>١. </bdi>١.</div> +<div><bdi>٢. </bdi>٢.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-103.html b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-103.html index 01b959cd85a..e2e01134d64 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-103.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/arabic-indic/css3-counter-styles-103.html @@ -5,13 +5,13 @@ <title>arabic-indic, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-103-ref.html'> <meta name="assert" content="list-style-type: arabic-indic produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: arabic-indic; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-006-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-006-ref.html new file mode 100644 index 00000000000..fa33991ec54 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-006-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>armenian, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: armenian produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"><ol> +<div><bdi>Ա. </bdi>Ա</div> +<div><bdi>Բ. </bdi>Բ</div> +<div><bdi>Գ. </bdi>Գ</div> +<div><bdi>Դ. </bdi>Դ</div> +<div><bdi>Ե. </bdi>Ե</div> +<div><bdi>Զ. </bdi>Զ</div> +<div><bdi>Է. </bdi>Է</div> +<div><bdi>Ը. </bdi>Ը</div> +<div><bdi>Թ. </bdi>Թ</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-006.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-006.html index dd1185067bc..7500274ce99 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-006.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-006.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-006-ref.html'> <meta name="assert" content="list-style-type: armenian produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-007-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-007-ref.html new file mode 100644 index 00000000000..fb1f29b2560 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-007-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>armenian, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: armenian produces numbers after 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>Ժ. </bdi>Ժ</div></ol> +<ol start='11'><div><bdi>ԺԱ. </bdi>ԺԱ</div></ol> +<ol start='12'><div><bdi>ԺԲ. </bdi>ԺԲ</div></ol> +<ol start='43'><div><bdi>ԽԳ. </bdi>ԽԳ</div></ol> +<ol start='77'><div><bdi>ՀԷ. </bdi>ՀԷ</div></ol> +<ol start='80'><div><bdi>Ձ. </bdi>Ձ</div></ol> +<ol start='99'><div><bdi>ՂԹ. </bdi>ՂԹ</div></ol> +<ol start='100'><div><bdi>Ճ. </bdi>Ճ</div></ol> +<ol start='101'><div><bdi>ՃԱ. </bdi>ՃԱ</div></ol> +<ol start='222'><div><bdi>ՄԻԲ. </bdi>ՄԻԲ</div></ol> +<ol start='540'><div><bdi>ՇԽ. </bdi>ՇԽ</div></ol> +<ol start='999'><div><bdi>ՋՂԹ. </bdi>ՋՂԹ</div></ol> +<ol start='1000'><div><bdi>Ռ. </bdi>Ռ</div></ol> +<ol start='1005'><div><bdi>ՌԵ. </bdi>ՌԵ</div></ol> +<ol start='1060'><div><bdi>ՌԿ. </bdi>ՌԿ</div></ol> +<ol start='1065'><div><bdi>ՌԿԵ. </bdi>ՌԿԵ</div></ol> +<ol start='1800'><div><bdi>ՌՊ. </bdi>ՌՊ</div></ol> +<ol start='1860'><div><bdi>ՌՊԿ. </bdi>ՌՊԿ</div></ol> +<ol start='1865'><div><bdi>ՌՊԿԵ. </bdi>ՌՊԿԵ</div></ol> +<ol start='5865'><div><bdi>ՐՊԿԵ. </bdi>ՐՊԿԵ</div></ol> +<ol start='7005'><div><bdi>ՒԵ. </bdi>ՒԵ</div></ol> +<ol start='7800'><div><bdi>ՒՊ. </bdi>ՒՊ</div></ol> +<ol start='7865'><div><bdi>ՒՊԿԵ. </bdi>ՒՊԿԵ</div></ol> +<ol start='9999'><div><bdi>ՔՋՂԹ. </bdi>ՔՋՂԹ</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-007.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-007.html index eb35fb14622..dd5653019cd 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-007.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-007.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-007-ref.html'> <meta name="assert" content="list-style-type: armenian produces numbers after 9 items per the spec."> <style type='text/css'> ol li { list-style-type: armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-008-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-008-ref.html new file mode 100644 index 00000000000..83cb399b412 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-008-ref.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>armenian, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: armenian produces counter values outside its ranges using its fallback style."> +<style type='text/css'> +ol li { list-style-type: armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='9999'><div><bdi>ՔՋՂԹ. </bdi>ՔՋՂԹ</div></ol> +<ol start='10000'><div><bdi>10000. </bdi>10000</div></ol> +<ol start='10001'><div><bdi>10001. </bdi>10001</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-008.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-008.html index 82440be6b35..0ea36af37cd 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-008.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-008.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-008-ref.html'> <meta name="assert" content="list-style-type: armenian produces counter values outside its ranges using its fallback style."> <style type='text/css'> ol li { list-style-type: armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-009-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-009-ref.html new file mode 100644 index 00000000000..c25351af551 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-009-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>armenian, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: armenian will produce a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class="test"><ol> +<div><bdi>Ա. </bdi>Ա.</div> +<div><bdi>Բ. </bdi>Բ.</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-009.html b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-009.html index b78a07cfeab..72db60d0f16 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-009.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/armenian/css3-counter-styles-009.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-009-ref.html'> <meta name="assert" content="list-style-type: armenian will produce a suffix per the spec."> <style type='text/css'> ol li { list-style-type: armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-116-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-116-ref.html new file mode 100644 index 00000000000..4b321096447 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-116-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>bengali, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:bengali produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: bengali; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>১. </bdi>১</div> +<div><bdi>২. </bdi>২</div> +<div><bdi>৩. </bdi>৩</div> +<div><bdi>৪. </bdi>৪</div> +<div><bdi>৫. </bdi>৫</div> +<div><bdi>৬. </bdi>৬</div> +<div><bdi>৭. </bdi>৭</div> +<div><bdi>৮. </bdi>৮</div> +<div><bdi>৯. </bdi>৯</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-116.html b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-116.html index d82af0d69da..2fc9bf6878b 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-116.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-116.html @@ -5,13 +5,13 @@ <title>bengali, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-116-ref.html'> <meta name="assert" content="list-style-type:bengali produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: bengali; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-117-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-117-ref.html new file mode 100644 index 00000000000..d84113d0fe5 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-117-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>bengali, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: bengali produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: bengali; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>১০. </bdi>১০</div></ol> +<ol start="11"><div><bdi>১১. </bdi>১১</div></ol> +<ol start="12"><div><bdi>১২. </bdi>১২</div></ol> +<ol start="43"><div><bdi>৪৩. </bdi>৪৩</div></ol> +<ol start="77"><div><bdi>৭৭. </bdi>৭৭</div></ol> +<ol start="80"><div><bdi>৮০. </bdi>৮০</div></ol> +<ol start="99"><div><bdi>৯৯. </bdi>৯৯</div></ol> +<ol start="100"><div><bdi>১০০. </bdi>১০০</div></ol> +<ol start="101"><div><bdi>১০১. </bdi>১০১</div></ol> +<ol start="222"><div><bdi>২২২. </bdi>২২২</div></ol> +<ol start="540"><div><bdi>৫৪০. </bdi>৫৪০</div></ol> +<ol start="999"><div><bdi>৯৯৯. </bdi>৯৯৯</div></ol> +<ol start="1000"><div><bdi>১০০০. </bdi>১০০০</div></ol> +<ol start="1005"><div><bdi>১০০৫. </bdi>১০০৫</div></ol> +<ol start="1060"><div><bdi>১০৬০. </bdi>১০৬০</div></ol> +<ol start="1065"><div><bdi>১০৬৫. </bdi>১০৬৫</div></ol> +<ol start="1800"><div><bdi>১৮০০. </bdi>১৮০০</div></ol> +<ol start="1860"><div><bdi>১৮৬০. </bdi>১৮৬০</div></ol> +<ol start="1865"><div><bdi>১৮৬৫. </bdi>১৮৬৫</div></ol> +<ol start="5865"><div><bdi>৫৮৬৫. </bdi>৫৮৬৫</div></ol> +<ol start="7005"><div><bdi>৭০০৫. </bdi>৭০০৫</div></ol> +<ol start="7800"><div><bdi>৭৮০০. </bdi>৭৮০০</div></ol> +<ol start="7864"><div><bdi>৭৮৬৪. </bdi>৭৮৬৪</div></ol> +<ol start="9999"><div><bdi>৯৯৯৯. </bdi>৯৯৯৯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-117.html b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-117.html index 9fe3ba66c33..58ba8400ec3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-117.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-117.html @@ -5,13 +5,13 @@ <title>bengali, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-117-ref.html'> <meta name="assert" content="list-style-type: bengali produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: bengali; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-118-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-118-ref.html new file mode 100644 index 00000000000..6ba63f6bc88 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-118-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>bengali, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: bengali produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: bengali; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>১. </bdi>১.</div> +<div><bdi>২. </bdi>২.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-118.html b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-118.html index 32c8c34be26..1215a1c201d 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-118.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/bengali/css3-counter-styles-118.html @@ -5,13 +5,13 @@ <title>bengali, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-118-ref.html'> <meta name="assert" content="list-style-type: bengali produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: bengali; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-158-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-158-ref.html new file mode 100644 index 00000000000..f511fa63de4 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-158-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cambodian, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cambodian produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: cambodian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>១. </bdi>១</div> +<div><bdi>២. </bdi>២</div> +<div><bdi>៣. </bdi>៣</div> +<div><bdi>៤. </bdi>៤</div> +<div><bdi>៥. </bdi>៥</div> +<div><bdi>៦. </bdi>៦</div> +<div><bdi>៧. </bdi>៧</div> +<div><bdi>៨. </bdi>៨</div> +<div><bdi>៩. </bdi>៩</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-158.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-158.html index ac7305c72cd..76154c86b20 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-158.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-158.html @@ -5,13 +5,13 @@ <title>cambodian, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-158-ref.html'> <meta name="assert" content="list-style-type: cambodian produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: cambodian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-159-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-159-ref.html new file mode 100644 index 00000000000..a966d9bad3a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-159-ref.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cambodian, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cambodian produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: cambodian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="11"><div><bdi>១១. </bdi>១១</div></ol> +<ol start="12"><div><bdi>១២. </bdi>១២</div></ol> +<ol start="43"><div><bdi>៤៣. </bdi>៤៣</div></ol> +<ol start="77"><div><bdi>៧៧. </bdi>៧៧</div></ol> +<ol start="80"><div><bdi>៨០. </bdi>៨០</div></ol> +<ol start="99"><div><bdi>៩៩. </bdi>៩៩</div></ol> +<ol start="100"><div><bdi>១០០. </bdi>១០០</div></ol> +<ol start="101"><div><bdi>១០១. </bdi>១០១</div></ol> +<ol start="222"><div><bdi>២២២. </bdi>២២២</div></ol> +<ol start="540"><div><bdi>៥៤០. </bdi>៥៤០</div></ol> +<ol start="999"><div><bdi>៩៩៩. </bdi>៩៩៩</div></ol> +<ol start="1000"><div><bdi>១០០០. </bdi>១០០០</div></ol> +<ol start="1005"><div><bdi>១០០៥. </bdi>១០០៥</div></ol> +<ol start="1060"><div><bdi>១០៦០. </bdi>១០៦០</div></ol> +<ol start="1065"><div><bdi>១០៦៥. </bdi>១០៦៥</div></ol> +<ol start="1800"><div><bdi>១៨០០. </bdi>១៨០០</div></ol> +<ol start="1860"><div><bdi>១៨៦០. </bdi>១៨៦០</div></ol> +<ol start="5865"><div><bdi>៥៨៦៥. </bdi>៥៨៦៥</div></ol> +<ol start="7005"><div><bdi>៧០០៥. </bdi>៧០០៥</div></ol> +<ol start="7800"><div><bdi>៧៨០០. </bdi>៧៨០០</div></ol> +<ol start="7864"><div><bdi>៧៨៦៤. </bdi>៧៨៦៤</div></ol> +<ol start="9999"><div><bdi>៩៩៩៩. </bdi>៩៩៩៩</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-159.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-159.html index cdb6bc4bfa7..d53f1e229fd 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-159.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-159.html @@ -5,13 +5,13 @@ <title>cambodian, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-159-ref.html'> <meta name="assert" content="list-style-type: cambodian produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: cambodian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-160-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-160-ref.html new file mode 100644 index 00000000000..c692b433213 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-160-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cambodian, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cambodian produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: cambodian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>១. </bdi>១.</div> +<div><bdi>២. </bdi>២.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-160.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-160.html index 0100bab98be..46223ab4daf 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-160.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cambodian/css3-counter-styles-160.html @@ -5,13 +5,13 @@ <title>cambodian, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-160-ref.html'> <meta name="assert" content="list-style-type: cambodian produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: cambodian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-001-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-001-ref.html new file mode 100644 index 00000000000..3bbb0a45890 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-001-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-decimal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cjk-decimal produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-decimal; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class='test'><ol> +<div><bdi>一、</bdi>一</div> +<div><bdi>二、</bdi>二</div> +<div><bdi>三、</bdi>三</div> +<div><bdi>四、</bdi>四</div> +<div><bdi>五、</bdi>五</div> +<div><bdi>六、</bdi>六</div> +<div><bdi>七、</bdi>七</div> +<div><bdi>八、</bdi>八</div> +<div><bdi>九、</bdi>九</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-001.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-001.html index 44c4bdf25e3..e5be28f7367 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-001.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-001.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-001-ref.html'> <meta name="assert" content="list-style-type: cjk-decimal produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: cjk-decimal; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-004-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-004-ref.html new file mode 100644 index 00000000000..3a1fd5a8a3d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-004-ref.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-decimal, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cjk-decimal produces numbers after 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-decimal; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class='test'> +<ol start="10"><div><bdi>一〇、</bdi>一〇</div></ol> +<ol start="11"><div><bdi>一一、</bdi>一一</div></ol> +<ol start="12"><div><bdi>一二、</bdi>一二</div></ol> +<ol start="43"><div><bdi>四三、</bdi>四三</div></ol> +<ol start="77"><div><bdi>七七、</bdi>七七</div></ol> +<ol start="80"><div><bdi>八〇、</bdi>八〇</div></ol> +<ol start="99"><div><bdi>九九、</bdi>九九</div></ol> +<ol start="100"><div><bdi>一〇〇、</bdi>一〇〇</div></ol> +<ol start="101"><div><bdi>一〇一、</bdi>一〇一</div></ol> +<ol start="222"><div><bdi>二二二、</bdi>二二二</div></ol> +<ol start="540"><div><bdi>五四〇、</bdi>五四〇</div></ol> +<ol start="999"><div><bdi>九九九、</bdi>九九九</div></ol> +<ol start="1000"><div><bdi>一〇〇〇、</bdi>一〇〇〇</div></ol> +<ol start="1005"><div><bdi>一〇〇五、</bdi>一〇〇五</div></ol> +<ol start="1060"><div><bdi>一〇六〇、</bdi>一〇六〇</div></ol> +<ol start="1065"><div><bdi>一〇六五、</bdi>一〇六五</div></ol> +<ol start="1800"><div><bdi>一八〇〇、</bdi>一八〇〇</div></ol> +<ol start="1860"><div><bdi>一八六〇、</bdi>一八六〇</div></ol> +<ol start="5865"><div><bdi>五八六五、</bdi>五八六五</div></ol> +<ol start="7005"><div><bdi>七〇〇五、</bdi>七〇〇五</div></ol> +<ol start="7800"><div><bdi>七八〇〇、</bdi>七八〇〇</div></ol> +<ol start="7864"><div><bdi>七八六四、</bdi>七八六四</div></ol> +<ol start="9999"><div><bdi>九九九九、</bdi>九九九九</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-004.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-004.html index 0cf970cd5ad..921c4ace80f 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-004.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-004.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-004-ref.html'> <meta name="assert" content="list-style-type: cjk-decimal produces numbers after 9 items per the spec."> <style type='text/css'> ol li { list-style-type: cjk-decimal; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-005-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-005-ref.html new file mode 100644 index 00000000000..79d5d69e9d3 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-005-ref.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-decimal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cjk-decimal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +ol li { list-style-type: cjk-decimal; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>一、</bdi>一、</div> +<div><bdi>二、</bdi>二、</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-005.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-005.html index 599323ff474..c059d354f24 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-005.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-decimal/css3-counter-styles-005.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-005-ref.html'> <meta name="assert" content="list-style-type: cjk-decimal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> ol li { list-style-type: cjk-decimal; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-201-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-201-ref.html new file mode 100644 index 00000000000..c916747e9b3 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-201-ref.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-earthly-branch, 0-12</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:cjk-earthly-branch produces numbers up to 12 per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-earthly-branch; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi dir=ltr>子、</bdi>子</div> +<div><bdi dir=ltr>丑、</bdi>丑</div> +<div><bdi dir=ltr>寅、</bdi>寅</div> +<div><bdi dir=ltr>卯、</bdi>卯</div> +<div><bdi dir=ltr>辰、</bdi>辰</div> +<div><bdi dir=ltr>巳、</bdi>巳</div> +<div><bdi dir=ltr>午、</bdi>午</div> +<div><bdi dir=ltr>未、</bdi>未</div> +<div><bdi dir=ltr>申、</bdi>申</div> +<div><bdi dir=ltr>酉、</bdi>酉</div> +<div><bdi dir=ltr>戌、</bdi>戌</div> +<div><bdi dir=ltr>亥、</bdi>亥</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-201.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-201.html index 5803371587a..730a7f7eca7 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-201.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-201.html @@ -5,13 +5,13 @@ <title>cjk-earthly-branch, 0-12</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-201-ref.html'> <meta name="assert" content="list-style-type:cjk-earthly-branch produces numbers up to 12 per the spec."> <style type='text/css'> ol li { list-style-type: cjk-earthly-branch; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-202-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-202-ref.html new file mode 100644 index 00000000000..371bf5c0600 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-202-ref.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-earthly-branch, 13+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cjk-earthly-branch produces numbers after 12 per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-earthly-branch; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="13"><div><bdi dir=ltr>子子、</bdi>子子</div></ol> +<ol start="14"><div><bdi dir=ltr>子丑、</bdi>子丑</div></ol> +<ol start="77"><div><bdi dir=ltr>巳辰、</bdi>巳辰</div></ol> +<ol start="80"><div><bdi dir=ltr>巳未、</bdi>巳未</div></ol> +<ol start="99"><div><bdi dir=ltr>未寅、</bdi>未寅</div></ol> +<ol start="100"><div><bdi dir=ltr>未卯、</bdi>未卯</div></ol> +<ol start="101"><div><bdi dir=ltr>未辰、</bdi>未辰</div></ol> +<ol start="222"><div><bdi dir=ltr>子巳巳、</bdi>子巳巳</div></ol> +<ol start="540"><div><bdi dir=ltr>寅未亥、</bdi>寅未亥</div></ol> +<ol start="999"><div><bdi dir=ltr>巳戌寅、</bdi>巳戌寅</div></ol> +<ol start="1000"><div><bdi dir=ltr>巳戌卯、</bdi>巳戌卯</div></ol> +<ol start="1005"><div><bdi dir=ltr>巳戌申、</bdi>巳戌申</div></ol> +<ol start="1060"><div><bdi dir=ltr>午卯卯、</bdi>午卯卯</div></ol> +<ol start="1065"><div><bdi dir=ltr>午卯申、</bdi>午卯申</div></ol> +<ol start="1800"><div><bdi dir=ltr>亥辰亥、</bdi>亥辰亥</div></ol> +<ol start="1860"><div><bdi dir=ltr>亥酉亥、</bdi>亥酉亥</div></ol> +<ol start="5865"><div><bdi dir=ltr>寅卯未申、</bdi>寅卯未申</div></ol> +<ol start="7005"><div><bdi dir=ltr>寅亥午申、</bdi>寅亥午申</div></ol> +<ol start="7800"><div><bdi dir=ltr>卯巳子亥、</bdi>卯巳子亥</div></ol> +<ol start="7864"><div><bdi dir=ltr>卯巳午卯、</bdi>卯巳午卯</div></ol> +<ol start="9999"><div><bdi dir=ltr>辰申辰寅、</bdi>辰申辰寅</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-202.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-202.html index d7c19b7f89a..909118fb109 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-202.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-202.html @@ -5,13 +5,13 @@ <title>cjk-earthly-branch, 13+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-202-ref.html'> <meta name="assert" content="list-style-type: cjk-earthly-branch produces numbers after 12 per the spec."> <style type='text/css'> ol li { list-style-type: cjk-earthly-branch; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-203-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-203-ref.html new file mode 100644 index 00000000000..357139cf891 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-203-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-earthly-branch, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cjk-earthly-branch produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-earthly-branch; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi dir=ltr>子、</bdi>子、</div> +<div><bdi dir=ltr>丑、</bdi>丑、</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-203.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-203.html index d7f3336ffb5..1b343bfcc27 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-203.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-earthly-branch/css3-counter-styles-203.html @@ -5,13 +5,13 @@ <title>cjk-earthly-branch, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-203-ref.html'> <meta name="assert" content="list-style-type: cjk-earthly-branch produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: cjk-earthly-branch; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-204-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-204-ref.html new file mode 100644 index 00000000000..ccffdeae759 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-204-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-heavenly-stem, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:cjk-heavenly-stem produces numbers up to 12 per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-heavenly-stem; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi dir=ltr>甲、</bdi>甲</div> +<div><bdi dir=ltr>乙、</bdi>乙</div> +<div><bdi dir=ltr>丙、</bdi>丙</div> +<div><bdi dir=ltr>丁、</bdi>丁</div> +<div><bdi dir=ltr>戊、</bdi>戊</div> +<div><bdi dir=ltr>己、</bdi>己</div> +<div><bdi dir=ltr>庚、</bdi>庚</div> +<div><bdi dir=ltr>辛、</bdi>辛</div> +<div><bdi dir=ltr>壬、</bdi>壬</div> +<div><bdi dir=ltr>癸、</bdi>癸</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-204.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-204.html index d96bf00f285..6ba9000d07c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-204.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-204.html @@ -5,13 +5,13 @@ <title>cjk-heavenly-stem, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-204-ref.html'> <meta name="assert" content="list-style-type:cjk-heavenly-stem produces numbers up to 12 per the spec."> <style type='text/css'> ol li { list-style-type: cjk-heavenly-stem; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-205-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-205-ref.html new file mode 100644 index 00000000000..c831c942e6c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-205-ref.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-heavenly-stem, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cjk-heavenly-stem produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-heavenly-stem; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="11"><div><bdi dir=ltr>甲甲、</bdi>甲甲</div></ol> +<ol start="12"><div><bdi dir=ltr>甲乙、</bdi>甲乙</div></ol> +<ol start="43"><div><bdi dir=ltr>丁丙、</bdi>丁丙</div></ol> +<ol start="77"><div><bdi dir=ltr>庚庚、</bdi>庚庚</div></ol> +<ol start="80"><div><bdi dir=ltr>庚癸、</bdi>庚癸</div></ol> +<ol start="99"><div><bdi dir=ltr>壬壬、</bdi>壬壬</div></ol> +<ol start="100"><div><bdi dir=ltr>壬癸、</bdi>壬癸</div></ol> +<ol start="101"><div><bdi dir=ltr>癸甲、</bdi>癸甲</div></ol> +<ol start="222"><div><bdi dir=ltr>乙乙乙、</bdi>乙乙乙</div></ol> +<ol start="540"><div><bdi dir=ltr>戊丙癸、</bdi>戊丙癸</div></ol> +<ol start="999"><div><bdi dir=ltr>壬壬壬、</bdi>壬壬壬</div></ol> +<ol start="1000"><div><bdi dir=ltr>壬壬癸、</bdi>壬壬癸</div></ol> +<ol start="1005"><div><bdi dir=ltr>壬癸戊、</bdi>壬癸戊</div></ol> +<ol start="1060"><div><bdi dir=ltr>癸戊癸、</bdi>癸戊癸</div></ol> +<ol start="1065"><div><bdi dir=ltr>癸己戊、</bdi>癸己戊</div></ol> +<ol start="1800"><div><bdi dir=ltr>甲庚壬癸、</bdi>甲庚壬癸</div></ol> +<ol start="1860"><div><bdi dir=ltr>甲辛戊癸、</bdi>甲辛戊癸</div></ol> +<ol start="5865"><div><bdi dir=ltr>戊辛己戊、</bdi>戊辛己戊</div></ol> +<ol start="7005"><div><bdi dir=ltr>己壬癸戊、</bdi>己壬癸戊</div></ol> +<ol start="7800"><div><bdi dir=ltr>庚庚壬癸、</bdi>庚庚壬癸</div></ol> +<ol start="7864"><div><bdi dir=ltr>庚辛己丁、</bdi>庚辛己丁</div></ol> +<ol start="9999"><div><bdi dir=ltr>壬壬壬壬、</bdi>壬壬壬壬</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-205.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-205.html index 73a4aee128d..fdd74e837d3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-205.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-205.html @@ -5,13 +5,13 @@ <title>cjk-heavenly-stem, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-205-ref.html'> <meta name="assert" content="list-style-type: cjk-heavenly-stem produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: cjk-heavenly-stem; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-206-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-206-ref.html new file mode 100644 index 00000000000..e5904cf70c5 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-206-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>cjk-heavenly-stem, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: cjk-heavenly-stem produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: cjk-heavenly-stem; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi dir=ltr>甲、</bdi>甲、</div> +<div><bdi dir=ltr>乙、</bdi>乙、</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-206.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-206.html index 0f47de46e99..9c7a4a0d2bb 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-206.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cjk-heavenly-stem/css3-counter-styles-206.html @@ -5,13 +5,13 @@ <title>cjk-heavenly-stem, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-206-ref.html'> <meta name="assert" content="list-style-type: cjk-heavenly-stem produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: cjk-heavenly-stem; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter-invalid.html new file mode 100644 index 00000000000..fd382553df8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter-invalid.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule additiveSymbols setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-additive-symbols-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: additive; + additive-symbols: 2 C, 1 B, 0 A; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside" start=0> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +foo_rule.additiveSymbols = ''; +foo_rule.additiveSymbols = 'A B C'; +foo_rule.additiveSymbols = '1 B, 2 C, 0 A'; +foo_rule.additiveSymbols = '2 C C, 1 B, 0 A'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter-ref.html new file mode 100644 index 00000000000..a09788e369a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule additiveSymbols setter</title> + +<ol> + <div>A.</div> + <div>B.</div> + <div>C.</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter.html new file mode 100644 index 00000000000..1ff6b424464 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-additive-symbols-setter.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule additiveSymbols setter</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-additive-symbols-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: additive; + additive-symbols: 1 I, 0 O; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside" start=0> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.additiveSymbols = '2 C, 1 B, 0 A'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter-invalid.html new file mode 100644 index 00000000000..c5c43a32237 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter-invalid.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule fallback setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-fallback-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: fixed; + symbols: A B; + fallback: lower-roman; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +foo_rule.fallback = 'none'; +foo_rule.fallback = 'lower-roman upper-roman' +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter-ref.html new file mode 100644 index 00000000000..da4bb598206 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule fallback setter</title> + +<ol> + <div>A.</div> + <div>B.</div> + <div>iii.</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter.html new file mode 100644 index 00000000000..399463f3f18 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-fallback-setter.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule fallback setter</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-fallback-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: fixed; + symbols: A B; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.fallback = 'lower-roman'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter-invalid.html new file mode 100644 index 00000000000..01edc415e90 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter-invalid.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule name setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-name-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: fixed; + symbols: A B C; +} + +@counter-style bar { + system: fixed; + symbols: X Y Z; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<ol style="list-style-type: bar; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +rule.name = ''; +rule.name = '123'; +rule.name = 'initial'; +rule.name = 'inherit'; +rule.name = 'unset'; +rule.name = 'none'; +rule.name = 'disc'; +rule.name = 'decimal'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter-ref.html new file mode 100644 index 00000000000..91251ad8436 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter-ref.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule name setter</title> + +<ol> + <div>A.</div> + <div>B.</div> + <div>C.</div> +</ol> + +<ol> + <div>X.</div> + <div>Y.</div> + <div>Z.</div> +</ol> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter.html new file mode 100644 index 00000000000..4cb926dd129 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-name-setter.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule name setter</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-name-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: fixed; + symbols: A B C; +} + +@counter-style bar { + system: fixed; + symbols: '1' '2' '3'; +} + +@counter-style foo { + system: fixed; + symbols: X Y Z; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<ol style="list-style-type: bar; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +// Change the last counter style name from 'foo' to 'bar' +const sheet = document.getElementById('sheet'); +const rule = sheet.sheet.rules[2]; +rule.name = 'bar'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter-invalid.html new file mode 100644 index 00000000000..e15447ba4dd --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter-invalid.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule negative setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-negative-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: extends decimal; + negative: '(' ')'; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside" start="-3"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +foo_rule.negative = 'X Y Z'; +foo_rule.negative = '"X" "Y" "Z"'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter-ref.html new file mode 100644 index 00000000000..7d465a3335e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule negative setter</title> + +<ol> + <div>(3).</div> + <div>(2).</div> + <div>(1).</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter.html new file mode 100644 index 00000000000..06238841ec5 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-negative-setter.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule negative setter</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-negative-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: extends decimal; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside" start="-3"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.negative = '"(" ")"'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter-invalid.html new file mode 100644 index 00000000000..c263a1bb5fa --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter-invalid.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule pad setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-pad-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: extends decimal; + pad: 3 '0'; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +foo_rule.pad = '-1 "0"'; +foo_rule.pad = '3'; +foo_rule.pad = '3 "X" "Y"'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter-ref.html new file mode 100644 index 00000000000..6184686f0ee --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule pad setter</title> + +<ol> + <div>001.</div> + <div>002.</div> + <div>003.</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter.html new file mode 100644 index 00000000000..df1732dae62 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-pad-setter.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule pad setter</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-pad-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: extends decimal; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.pad = '3 "0"'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter-invalid.html new file mode 100644 index 00000000000..7aba3a0a5b9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter-invalid.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule prefix and suffix setters with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-prefix-suffix-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: cyclic; + symbols: A B C; + prefix: '('; + suffix: ')'; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +foo_rule.prefix = '"(" "("'; +foo_rule.prefix = ')'; +foo_rule.prefix = '123'; + +foo_rule.suffix = '")" ")"'; +foo_rule.suffix = '('; +foo_rule.suffix = '456'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter-ref.html new file mode 100644 index 00000000000..bf52d54adbc --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule prefix and suffix setters</title> + +<ol> + <div>(A)</div> + <div>(B)</div> + <div>(C)</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter.html new file mode 100644 index 00000000000..899caa26ba7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-prefix-suffix-setter.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule prefix and suffix setters</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-prefix-suffix-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: cyclic; + symbols: A B C; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.prefix = '"("'; +foo_rule.suffix = '")"'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter-invalid.html new file mode 100644 index 00000000000..2fc459551f0 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter-invalid.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule range setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-range-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: cyclic; + symbols: A B C; + range: 1 2; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +foo_rule.range = "1 2 3"; +foo_rule.range = "3 1" +foo_rule.range = "1 infinity" +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter-ref.html new file mode 100644 index 00000000000..0129b467c83 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule range setter</title> + +<ol> + <div>A.</div> + <div>B.</div> + <div>3.</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter.html new file mode 100644 index 00000000000..10d94f0cdb3 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-range-setter.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule range setter</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-range-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: cyclic; + symbols: A B C; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.range = "1 2"; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter-invalid.html new file mode 100644 index 00000000000..3b40b0d4a3b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter-invalid.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule symbols setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-symbols-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: alphabetic; + symbols: A B C; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Invalid values should be ignored +foo_rule.symbols = ''; +foo_rule.symbols = '1 2 *'; +foo_rule.symbols = 'A'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter-ref.html new file mode 100644 index 00000000000..64967db5ab9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule symbols setter</title> + +<ol> + <div>A.</div> + <div>B.</div> + <div>C.</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter.html new file mode 100644 index 00000000000..cd9f66d2386 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-symbols-setter.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule symbols setter</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-symbols-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: cyclic; + symbols: X Y Z; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.symbols = "A B C"; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-1.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-1.html new file mode 100644 index 00000000000..a616a60e0b0 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-1.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule system setter with 'fixed' system</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-system-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: fixed; + symbols: A B C; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside" start=0> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.system = "fixed 0"; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-2.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-2.html new file mode 100644 index 00000000000..f1cc65d7fcc --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-2.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule system setter with 'extends' system</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-system-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: extends decimal; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; +foo_rule.system = "extends upper-alpha"; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-invalid.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-invalid.html new file mode 100644 index 00000000000..e56ec1a23e4 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-invalid.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule system setter with invalid values</title> +<link rel="help" href="https://www.w3.org/TR/css-counter-styles-3/#the-csscounterstylerule-interface"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="match" href="cssom-system-setter-ref.html"> +<style id="sheet"> +@counter-style foo { + system: fixed; + symbols: A B C; +} +</style> + +<ol style="list-style-type: foo; list-style-position: inside"> + <li></li> + <li></li> + <li></li> +</ol> + +<script> +// Force layout update before changing the rule +document.body.offsetWidth; + +const sheet = document.getElementById('sheet'); +const foo_rule = sheet.sheet.rules[0]; + +// Values with syntax errors should be ignored +foo_rule.system = '123'; +foo_rule.system = 'extends none'; +foo_rule.system = 'extends decimal decimal'; + +// Values changing algorithm should be ignored +foo_rule.system = 'numeric'; +foo_rule.system = 'extends lower-roman'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-ref.html new file mode 100644 index 00000000000..98bd994659f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/cssom/cssom-system-setter-ref.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<title>CSSCounterStyleRule system setter</title> + +<ol> + <div>A.</div> + <div>B.</div> + <div>C.</div> +</ol> + diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-119-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-119-ref.html new file mode 100644 index 00000000000..06a8bf9b30e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-119-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>devanagari, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:devanagari produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: devanagari; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>१. </bdi>१</div> +<div><bdi>२. </bdi>२</div> +<div><bdi>३. </bdi>३</div> +<div><bdi>४. </bdi>४</div> +<div><bdi>५. </bdi>५</div> +<div><bdi>६. </bdi>६</div> +<div><bdi>७. </bdi>७</div> +<div><bdi>८. </bdi>८</div> +<div><bdi>९. </bdi>९</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-119.html b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-119.html index 464925184b2..c736f7f5a32 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-119.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-119.html @@ -5,13 +5,13 @@ <title>devanagari, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-119-ref.html'> <meta name="assert" content="list-style-type:devanagari produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: devanagari; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-120-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-120-ref.html new file mode 100644 index 00000000000..974530512cb --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-120-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>devanagari, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: devanagari produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: devanagari; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>१०. </bdi>१०</div></ol> +<ol start="11"><div><bdi>११. </bdi>११</div></ol> +<ol start="12"><div><bdi>१२. </bdi>१२</div></ol> +<ol start="43"><div><bdi>४३. </bdi>४३</div></ol> +<ol start="77"><div><bdi>७७. </bdi>७७</div></ol> +<ol start="80"><div><bdi>८०. </bdi>८०</div></ol> +<ol start="99"><div><bdi>९९. </bdi>९९</div></ol> +<ol start="100"><div><bdi>१००. </bdi>१००</div></ol> +<ol start="101"><div><bdi>१०१. </bdi>१०१</div></ol> +<ol start="222"><div><bdi>२२२. </bdi>२२२</div></ol> +<ol start="540"><div><bdi>५४०. </bdi>५४०</div></ol> +<ol start="999"><div><bdi>९९९. </bdi>९९९</div></ol> +<ol start="1000"><div><bdi>१०००. </bdi>१०००</div></ol> +<ol start="1005"><div><bdi>१००५. </bdi>१००५</div></ol> +<ol start="1060"><div><bdi>१०६०. </bdi>१०६०</div></ol> +<ol start="1065"><div><bdi>१०६५. </bdi>१०६५</div></ol> +<ol start="1800"><div><bdi>१८००. </bdi>१८००</div></ol> +<ol start="1860"><div><bdi>१८६०. </bdi>१८६०</div></ol> +<ol start="1865"><div><bdi>१८६५. </bdi>१८६५</div></ol> +<ol start="5865"><div><bdi>५८६५. </bdi>५८६५</div></ol> +<ol start="7005"><div><bdi>७००५. </bdi>७००५</div></ol> +<ol start="7800"><div><bdi>७८००. </bdi>७८००</div></ol> +<ol start="7864"><div><bdi>७८६४. </bdi>७८६४</div></ol> +<ol start="9999"><div><bdi>९९९९. </bdi>९९९९</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-120.html b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-120.html index 29d10c08030..e52763b761c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-120.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-120.html @@ -5,13 +5,13 @@ <title>devanagari, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-120-ref.html'> <meta name="assert" content="list-style-type: devanagari produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: devanagari; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-121-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-121-ref.html new file mode 100644 index 00000000000..eacfc22f025 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-121-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>devanagari, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: devanagari produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: devanagari; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>१. </bdi>१.</div> +<div><bdi>२. </bdi>२.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-121.html b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-121.html index c50676429bd..8db16780da3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-121.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/devanagari/css3-counter-styles-121.html @@ -5,13 +5,13 @@ <title>devanagari, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-121-ref.html'> <meta name="assert" content="list-style-type: devanagari produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: devanagari; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-010-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-010-ref.html new file mode 100644 index 00000000000..2b8663c5e37 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-010-ref.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>georgian, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: georgian produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: georgian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"><ol> +<div><bdi>ა. </bdi>ა</div> +<div><bdi>ბ. </bdi>ბ</div> +<div><bdi>გ. </bdi>გ</div> +<div><bdi>დ. </bdi>დ</div> +<div><bdi>ე. </bdi>ე</div> +<div><bdi>ვ. </bdi>ვ</div> +<div><bdi>ზ. </bdi>ზ</div> +<div><bdi>ჱ. </bdi>ჱ</div> +<div><bdi>თ. </bdi>თ</div></ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-010.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-010.html index 106f4cdc4bf..daed9f4151f 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-010.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-010.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-010-ref.html'> <meta name="assert" content="list-style: georgian produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: georgian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-011-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-011-ref.html new file mode 100644 index 00000000000..b6b2749e437 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-011-ref.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>georgian, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: georgian produces numbers after 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: georgian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>ი. </bdi>ი</div></ol> +<ol start='11'><div><bdi>ია. </bdi>ია</div></ol> +<ol start='12'><div><bdi>იბ. </bdi>იბ</div></ol> +<ol start='43'><div><bdi>მგ. </bdi>მგ</div></ol> +<ol start='77'><div><bdi>ოზ. </bdi>ოზ</div></ol> +<ol start='80'><div><bdi>პ. </bdi>პ</div></ol> +<ol start='99'><div><bdi>ჟთ. </bdi>ჟთ</div></ol> +<ol start='100'><div><bdi>რ. </bdi>რ</div></ol> +<ol start='101'><div><bdi>რა. </bdi>რა</div></ol> +<ol start='222'><div><bdi>სკბ. </bdi>სკბ</div></ol> +<ol start='540'><div><bdi>ფმ. </bdi>ფმ</div></ol> +<ol start='999'><div><bdi>შჟთ. </bdi>შჟთ</div></ol> +<ol start='1000'><div><bdi>ჩ. </bdi>ჩ</div></ol> +<ol start='1005'><div><bdi>ჩე. </bdi>ჩე</div></ol> +<ol start='1060'><div><bdi>ჩჲ. </bdi>ჩჲ</div></ol> +<ol start='1065'><div><bdi>ჩჲე. </bdi>ჩჲე</div></ol> +<ol start='1800'><div><bdi>ჩყ. </bdi>ჩყ</div></ol> +<ol start='1860'><div><bdi>ჩყჲ. </bdi>ჩყჲ</div></ol> +<ol start='1865'><div><bdi>ჩყჲე. </bdi>ჩყჲე</div></ol> +<ol start='5865'><div><bdi>ჭყჲე. </bdi>ჭყჲე</div></ol> +<ol start='7005'><div><bdi>ჴე. </bdi>ჴე</div></ol> +<ol start='7800'><div><bdi>ჴყ. </bdi>ჴყ</div></ol> +<ol start='7865'><div><bdi>ჴყჲე. </bdi>ჴყჲე</div></ol> +<ol start='9999'><div><bdi>ჰშჟთ. </bdi>ჰშჟთ</div></ol> +<ol start='10000'><div><bdi>ჵ. </bdi>ჵ</div></ol> +<ol start='10001'><div><bdi>ჵა. </bdi>ჵა</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-011.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-011.html index 932045ca09b..d09bc3f8bb4 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-011.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-011.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-011-ref.html'> <meta name="assert" content="list-style: georgian produces numbers after 9 items per the spec."> <style type='text/css'> ol li { list-style-type: georgian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-012-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-012-ref.html new file mode 100644 index 00000000000..17379ca6bed --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-012-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>georgian, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: georgian produces numbers in the fallback counter style above the limit per the spec."> +<style type='text/css'> +ol li { list-style-type: georgian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="19999"> +<div><bdi>ჵჰშჟთ. </bdi>ჵჰშჟთ</div> +<div><bdi>20000. </bdi>20000</div> +<div><bdi>20001. </bdi>20001</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-012.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-012.html index 6ad5f765970..df68197a1d0 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-012.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-012.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-012-ref.html'> <meta name="assert" content="list-style-type: georgian produces numbers in the fallback counter style above the limit per the spec."> <style type='text/css'> ol li { list-style-type: georgian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-014-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-014-ref.html new file mode 100644 index 00000000000..1c1cf286309 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-014-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>georgian, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: georgian produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: georgian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class="test"> +<ol start='1'><div><bdi>ა. </bdi>ა.</div></ol> +<ol start='2'><div><bdi>ბ. </bdi>ბ.</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-014.html b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-014.html index 24ae1d08bb9..41fc2b40269 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-014.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/georgian/css3-counter-styles-014.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-014-ref.html'> <meta name="assert" content="list-style-type: georgian produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: georgian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-122-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-122-ref.html new file mode 100644 index 00000000000..c6d99d354c7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-122-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>gujarati, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:gujarati produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: gujarati; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>૧. </bdi>૧</div> +<div><bdi>૨. </bdi>૨</div> +<div><bdi>૩. </bdi>૩</div> +<div><bdi>૪. </bdi>૪</div> +<div><bdi>૫. </bdi>૫</div> +<div><bdi>૬. </bdi>૬</div> +<div><bdi>૭. </bdi>૭</div> +<div><bdi>૮. </bdi>૮</div> +<div><bdi>૯. </bdi>૯</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-122.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-122.html index b4aa7b30de5..2e9bc503562 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-122.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-122.html @@ -5,13 +5,13 @@ <title>gujarati, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-122-ref.html'> <meta name="assert" content="list-style-type:gujarati produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: gujarati; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-123-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-123-ref.html new file mode 100644 index 00000000000..fd8e5b6fc2a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-123-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>gujarati, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: gujarati produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: gujarati; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>૧૦. </bdi>૧૦</div></ol> +<ol start="11"><div><bdi>૧૧. </bdi>૧૧</div></ol> +<ol start="12"><div><bdi>૧૨. </bdi>૧૨</div></ol> +<ol start="43"><div><bdi>૪૩. </bdi>૪૩</div></ol> +<ol start="77"><div><bdi>૭૭. </bdi>૭૭</div></ol> +<ol start="80"><div><bdi>૮૦. </bdi>૮૦</div></ol> +<ol start="99"><div><bdi>૯૯. </bdi>૯૯</div></ol> +<ol start="100"><div><bdi>૧૦૦. </bdi>૧૦૦</div></ol> +<ol start="101"><div><bdi>૧૦૧. </bdi>૧૦૧</div></ol> +<ol start="222"><div><bdi>૨૨૨. </bdi>૨૨૨</div></ol> +<ol start="540"><div><bdi>૫૪૦. </bdi>૫૪૦</div></ol> +<ol start="999"><div><bdi>૯૯૯. </bdi>૯૯૯</div></ol> +<ol start="1000"><div><bdi>૧૦૦૦. </bdi>૧૦૦૦</div></ol> +<ol start="1005"><div><bdi>૧૦૦૫. </bdi>૧૦૦૫</div></ol> +<ol start="1060"><div><bdi>૧૦૬૦. </bdi>૧૦૬૦</div></ol> +<ol start="1065"><div><bdi>૧૦૬૫. </bdi>૧૦૬૫</div></ol> +<ol start="1800"><div><bdi>૧૮૦૦. </bdi>૧૮૦૦</div></ol> +<ol start="1860"><div><bdi>૧૮૬૦. </bdi>૧૮૬૦</div></ol> +<ol start="1865"><div><bdi>૧૮૬૫. </bdi>૧૮૬૫</div></ol> +<ol start="5865"><div><bdi>૫૮૬૫. </bdi>૫૮૬૫</div></ol> +<ol start="7005"><div><bdi>૭૦૦૫. </bdi>૭૦૦૫</div></ol> +<ol start="7800"><div><bdi>૭૮૦૦. </bdi>૭૮૦૦</div></ol> +<ol start="7864"><div><bdi>૭૮૬૪. </bdi>૭૮૬૪</div></ol> +<ol start="9999"><div><bdi>૯૯૯૯. </bdi>૯૯૯૯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-123.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-123.html index 578601ad4ea..97f7e943961 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-123.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-123.html @@ -5,13 +5,13 @@ <title>gujarati, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-123-ref.html'> <meta name="assert" content="list-style-type: gujarati produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: gujarati; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-124-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-124-ref.html new file mode 100644 index 00000000000..996d5e5f178 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-124-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>gujarati, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: gujarati produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: gujarati; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>૧. </bdi>૧.</div> +<div><bdi>૨. </bdi>૨.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-124.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-124.html index 83beaabc14f..e9abca06f17 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-124.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gujarati/css3-counter-styles-124.html @@ -5,13 +5,13 @@ <title>gujarati, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-124-ref.html'> <meta name="assert" content="list-style-type: gujarati produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: gujarati; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-125-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-125-ref.html new file mode 100644 index 00000000000..16ed127909b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-125-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>gurmukhi, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:gurmukhi produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: gurmukhi; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>੧. </bdi>੧</div> +<div><bdi>੨. </bdi>੨</div> +<div><bdi>੩. </bdi>੩</div> +<div><bdi>੪. </bdi>੪</div> +<div><bdi>੫. </bdi>੫</div> +<div><bdi>੬. </bdi>੬</div> +<div><bdi>੭. </bdi>੭</div> +<div><bdi>੮. </bdi>੮</div> +<div><bdi>੯. </bdi>੯</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-125.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-125.html index 85d233c6472..1c1e882eb53 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-125.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-125.html @@ -5,13 +5,13 @@ <title>gurmukhi, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-125-ref.html'> <meta name="assert" content="list-style-type:gurmukhi produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: gurmukhi; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-126-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-126-ref.html new file mode 100644 index 00000000000..4b3b373e7b2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-126-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>gurmukhi, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: gurmukhi produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: gurmukhi; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>੧੦. </bdi>੧੦</div></ol> +<ol start="11"><div><bdi>੧੧. </bdi>੧੧</div></ol> +<ol start="12"><div><bdi>੧੨. </bdi>੧੨</div></ol> +<ol start="43"><div><bdi>੪੩. </bdi>੪੩</div></ol> +<ol start="77"><div><bdi>੭੭. </bdi>੭੭</div></ol> +<ol start="80"><div><bdi>੮੦. </bdi>੮੦</div></ol> +<ol start="99"><div><bdi>੯੯. </bdi>੯੯</div></ol> +<ol start="100"><div><bdi>੧੦੦. </bdi>੧੦੦</div></ol> +<ol start="101"><div><bdi>੧੦੧. </bdi>੧੦੧</div></ol> +<ol start="222"><div><bdi>੨੨੨. </bdi>੨੨੨</div></ol> +<ol start="540"><div><bdi>੫੪੦. </bdi>੫੪੦</div></ol> +<ol start="999"><div><bdi>੯੯੯. </bdi>੯੯੯</div></ol> +<ol start="1000"><div><bdi>੧੦੦੦. </bdi>੧੦੦੦</div></ol> +<ol start="1005"><div><bdi>੧੦੦੫. </bdi>੧੦੦੫</div></ol> +<ol start="1060"><div><bdi>੧੦੬੦. </bdi>੧੦੬੦</div></ol> +<ol start="1065"><div><bdi>੧੦੬੫. </bdi>੧੦੬੫</div></ol> +<ol start="1800"><div><bdi>੧੮੦੦. </bdi>੧੮੦੦</div></ol> +<ol start="1860"><div><bdi>੧੮੬੦. </bdi>੧੮੬੦</div></ol> +<ol start="1865"><div><bdi>੧੮੬੫. </bdi>੧੮੬੫</div></ol> +<ol start="5865"><div><bdi>੫੮੬੫. </bdi>੫੮੬੫</div></ol> +<ol start="7005"><div><bdi>੭੦੦੫. </bdi>੭੦੦੫</div></ol> +<ol start="7800"><div><bdi>੭੮੦੦. </bdi>੭੮੦੦</div></ol> +<ol start="7864"><div><bdi>੭੮੬੪. </bdi>੭੮੬੪</div></ol> +<ol start="9999"><div><bdi>੯੯੯੯. </bdi>੯੯੯੯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-126.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-126.html index 051cdc4d3bb..a113b31b6b4 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-126.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-126.html @@ -5,13 +5,13 @@ <title>gurmukhi, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-126-ref.html'> <meta name="assert" content="list-style-type: gurmukhi produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: gurmukhi; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-127-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-127-ref.html new file mode 100644 index 00000000000..26c3821b959 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-127-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>gurmukhi, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: gurmukhi produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: gurmukhi; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>੧. </bdi>੧.</div> +<div><bdi>੨. </bdi>੨.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-127.html b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-127.html index 52ab4808470..9c58d36e0e1 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-127.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/gurmukhi/css3-counter-styles-127.html @@ -5,13 +5,13 @@ <title>gurmukhi, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-127-ref.html'> <meta name="assert" content="list-style-type: gurmukhi produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: gurmukhi; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-015-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-015-ref.html new file mode 100644 index 00000000000..29f6bacba89 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-015-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hebrew, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: hebrew produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: hebrew; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"><ol> +<div><bdi dir=ltr>א. </bdi>א</div> +<div><bdi dir=ltr>ב. </bdi>ב</div> +<div><bdi dir=ltr>ג. </bdi>ג</div> +<div><bdi dir=ltr>ד. </bdi>ד</div> +<div><bdi dir=ltr>ה. </bdi>ה</div> +<div><bdi dir=ltr>ו. </bdi>ו</div> +<div><bdi dir=ltr>ז. </bdi>ז</div> +<div><bdi dir=ltr>ח. </bdi>ח</div> +<div><bdi dir=ltr>ט. </bdi>ט</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-015.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-015.html index 745cac5414b..4bebc4e9ebb 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-015.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-015.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-015-ref.html'> <meta name="assert" content="list-style: hebrew produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: hebrew; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016-ref.html new file mode 100644 index 00000000000..3a76add684d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hebrew, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: hebrew produces numbers after 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: hebrew; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi dir=ltr>י. </bdi>י</div></ol> +<ol start='11'><div><bdi dir=ltr>יא. </bdi>יא</div></ol> +<ol start='12'><div><bdi dir=ltr>יב. </bdi>יב</div></ol> +<ol start='13'><div><bdi dir=ltr>יג. </bdi>יג</div></ol> +<ol start='14'><div><bdi dir=ltr>יד. </bdi>יד</div></ol> +<ol start='15'><div><bdi dir=ltr>טו. </bdi>טו</div></ol> +<ol start='16'><div><bdi dir=ltr>טז. </bdi>טז</div></ol> +<ol start='17'><div><bdi dir=ltr>יז. </bdi>יז</div></ol> +<ol start='18'><div><bdi dir=ltr>יח. </bdi>יח</div></ol> +<ol start='43'><div><bdi dir=ltr>מג. </bdi>מג</div></ol> +<ol start='77'><div><bdi dir=ltr>עז. </bdi>עז</div></ol> +<ol start='80'><div><bdi dir=ltr>פ. </bdi>פ</div></ol> +<ol start='99'><div><bdi dir=ltr>צט. </bdi>צט</div></ol> +<ol start='100'><div><bdi dir=ltr>ק. </bdi>ק</div></ol> +<ol start='101'><div><bdi dir=ltr>קא. </bdi>קא</div></ol> +<ol start='222'><div><bdi dir=ltr>רכב. </bdi>רכב</div></ol> +<ol start='400'><div><bdi dir=ltr>ת. </bdi>ת</div></ol> +<ol start='401'><div><bdi dir=ltr>תא. </bdi>תא</div></ol> +<ol start='499'><div><bdi dir=ltr>תצט. </bdi>תצט</div></ol> +<ol start='500'><div><bdi dir=ltr>תק. </bdi>תק</div></ol> +<ol start='555'><div><bdi dir=ltr>תקנה. </bdi>תקנה</div></ol> +<ol start='997'><div><bdi dir=ltr>תתקצז. </bdi>תתקצז</div></ol> +<ol start='1000'><div><bdi dir=ltr>א׳. </bdi>א׳</div></ol> +<ol start='1001'><div><bdi dir=ltr>א׳א. </bdi>א׳א</div></ol> +<ol start='3256'><div><bdi dir=ltr>ג׳רנו. </bdi>ג׳רנו</div></ol> +<ol start='7998'><div><bdi dir=ltr>ז׳תתקצח. </bdi>ז׳תתקצח</div></ol> +<ol start='9999'><div><bdi dir=ltr>ט׳תתקצט. </bdi>ט׳תתקצט</div></ol> +<ol start='10000'><div><bdi dir=ltr>י׳. </bdi>י׳</div></ol> +<ol start='10997'><div><bdi dir=ltr>י׳תתקצז. </bdi>י׳תתקצז</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016.html index 6973f823dbe..77a7ac2cec1 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-016-ref.html'> <meta name="assert" content="list-style: hebrew produces numbers after 9 items per the spec."> <style type='text/css'> ol li { list-style-type: hebrew; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016a-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016a-ref.html new file mode 100644 index 00000000000..e673ae637bb --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016a-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hebrew, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: hebrew produces numbers in the fallback counter style above the limit per the spec."> +<style type='text/css'> +ol li { list-style-type: hebrew; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10999"> +<div><bdi dir=ltr>י׳תתקצט. </bdi>י׳תתקצט</div> +<div><bdi dir=ltr>11000. </bdi>11000</div> +<div><bdi dir=ltr>11001. </bdi>11001</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016a.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016a.html index 8957e06bbe5..65084217fd9 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016a.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-016a.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-016a-ref.html'> <meta name="assert" content="list-style-type: hebrew produces numbers in the fallback counter style above the limit per the spec."> <style type='text/css'> ol li { list-style-type: hebrew; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-017-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-017-ref.html new file mode 100644 index 00000000000..69dff036f36 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-017-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hebrew, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: hebrew produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: hebrew; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class="test"><ol> +<div><bdi dir=ltr>א. </bdi>א.</div> +<div><bdi dir=ltr>ב. </bdi>ב.</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-017.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-017.html index f80966a9c09..789fdf7f238 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-017.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hebrew/css3-counter-styles-017.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-017-ref.html'> <meta name="assert" content="list-style: hebrew produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: hebrew; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-033-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-033-ref.html new file mode 100644 index 00000000000..2414aab154a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-033-ref.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hiragana-iroha, simple</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to hiragana-iroha will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: hiragana-iroha; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol><div><bdi>い、</bdi>い、</div> +<div><bdi>ろ、</bdi>ろ、</div> +<div><bdi>は、</bdi>は、</div> +<div><bdi>に、</bdi>に、</div> +<div><bdi>ほ、</bdi>ほ、</div> +<div><bdi>へ、</bdi>へ、</div> +<div><bdi>と、</bdi>と、</div> +<div><bdi>ち、</bdi>ち、</div> +<div><bdi>り、</bdi>り、</div> +<div><bdi>ぬ、</bdi>ぬ、</div> +<div><bdi>る、</bdi>る、</div> +<div><bdi>を、</bdi>を、</div> +<div><bdi>わ、</bdi>わ、</div> +<div><bdi>か、</bdi>か、</div> +<div><bdi>よ、</bdi>よ、</div> +<div><bdi>た、</bdi>た、</div> +<div><bdi>れ、</bdi>れ、</div> +<div><bdi>そ、</bdi>そ、</div> +<div><bdi>つ、</bdi>つ、</div> +<div><bdi>ね、</bdi>ね、</div> +<div><bdi>な、</bdi>な、</div> +<div><bdi>ら、</bdi>ら、</div> +<div><bdi>む、</bdi>む、</div> +<div><bdi>う、</bdi>う、</div> +<div><bdi>ゐ、</bdi>ゐ、</div> +<div><bdi>の、</bdi>の、</div> +<div><bdi>お、</bdi>お、</div> +<div><bdi>く、</bdi>く、</div> +<div><bdi>や、</bdi>や、</div> +<div><bdi>ま、</bdi>ま、</div> +<div><bdi>け、</bdi>け、</div> +<div><bdi>ふ、</bdi>ふ、</div> +<div><bdi>こ、</bdi>こ、</div> +<div><bdi>え、</bdi>え、</div> +<div><bdi>て、</bdi>て、</div> +<div><bdi>あ、</bdi>あ、</div> +<div><bdi>さ、</bdi>さ、</div> +<div><bdi>き、</bdi>き、</div> +<div><bdi>ゆ、</bdi>ゆ、</div> +<div><bdi>め、</bdi>め、</div> +<div><bdi>み、</bdi>み、</div> +<div><bdi>し、</bdi>し、</div> +<div><bdi>ゑ、</bdi>ゑ、</div> +<div><bdi>ひ、</bdi>ひ、</div> +<div><bdi>も、</bdi>も、</div> +<div><bdi>せ、</bdi>せ、</div> +<div><bdi>す、</bdi>す、</div> +</ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-033.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-033.html index be962ea16f9..7011527bd15 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-033.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-033.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-033-ref.html'> <meta name="assert" content="Setting list-style-type to hiragana-iroha will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: hiragana-iroha; } +ol { list-style-type: hiragana-iroha; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-034-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-034-ref.html new file mode 100644 index 00000000000..11bf43b6587 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-034-ref.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hiragana-iroha, extended</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to hiragana-iroha will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: hiragana-iroha; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='48'><div><bdi>いい、</bdi>いい、</div></ol> +<ol start='77'><div><bdi>いま、</bdi>いま、</div></ol> +<ol start='80'><div><bdi>いこ、</bdi>いこ、</div></ol> +<ol start='99'><div><bdi>ろほ、</bdi>ろほ、</div></ol> +<ol start='100'><div><bdi>ろへ、</bdi>ろへ、</div></ol> +<ol start='101'><div><bdi>ろと、</bdi>ろと、</div></ol> +<ol start='222'><div><bdi>にえ、</bdi>にえ、</div></ol> +<ol start='540'><div><bdi>るむ、</bdi>るむ、</div></ol> +<ol start='999'><div><bdi>なを、</bdi>なを、</div></ol> +<ol start='1000'><div><bdi>なわ、</bdi>なわ、</div></ol> +<ol start='1005'><div><bdi>なそ、</bdi>なそ、</div></ol> +<ol start='1060'><div><bdi>らの、</bdi>らの、</div></ol> +<ol start='1065'><div><bdi>らけ、</bdi>らけ、</div></ol> +<ol start='1800'><div><bdi>きか、</bdi>きか、</div></ol> +<ol start='1860'><div><bdi>ゆお、</bdi>ゆお、</div></ol> +<ol start='5865'><div><bdi>ろまさ、</bdi>ろまさ、</div></ol> +<ol start='7005'><div><bdi>はちろ、</bdi>はちろ、</div></ol> +<ol start='7800'><div><bdi>はうも、</bdi>はうも、</div></ol> +<ol start='7864'><div><bdi>はのよ、</bdi>はのよ、</div></ol> +<ol start='9999'><div><bdi>にうて、</bdi>にうて、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-034.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-034.html index 69d04634f9b..98a07b43b47 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-034.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-034.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-034-ref.html'> <meta name="assert" content="Setting list-style-type to hiragana-iroha will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: hiragana-iroha; } +ol { list-style-type: hiragana-iroha; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-035-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-035-ref.html new file mode 100644 index 00000000000..7547c84fe26 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-035-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hiragana-iroha, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to hiragana-iroha will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: hiragana-iroha; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>い、</bdi>い、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-035.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-035.html index 634440def3a..a667031e2b1 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-035.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana-iroha/css3-counter-styles-035.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-035-ref.html'> <meta name="assert" content="Setting list-style-type to hiragana-iroha will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: hiragana-iroha; } +ol { list-style-type: hiragana-iroha; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-030-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-030-ref.html new file mode 100644 index 00000000000..6d62f1fbb5c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-030-ref.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hiragana, simple</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to hiragana will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: hiragana; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol><div><bdi>あ、</bdi>あ、</div> +<div><bdi>い、</bdi>い、</div> +<div><bdi>う、</bdi>う、</div> +<div><bdi>え、</bdi>え、</div> +<div><bdi>お、</bdi>お、</div> +<div><bdi>か、</bdi>か、</div> +<div><bdi>き、</bdi>き、</div> +<div><bdi>く、</bdi>く、</div> +<div><bdi>け、</bdi>け、</div> +<div><bdi>こ、</bdi>こ、</div> +<div><bdi>さ、</bdi>さ、</div> +<div><bdi>し、</bdi>し、</div> +<div><bdi>す、</bdi>す、</div> +<div><bdi>せ、</bdi>せ、</div> +<div><bdi>そ、</bdi>そ、</div> +<div><bdi>た、</bdi>た、</div> +<div><bdi>ち、</bdi>ち、</div> +<div><bdi>つ、</bdi>つ、</div> +<div><bdi>て、</bdi>て、</div> +<div><bdi>と、</bdi>と、</div> +<div><bdi>な、</bdi>な、</div> +<div><bdi>に、</bdi>に、</div> +<div><bdi>ぬ、</bdi>ぬ、</div> +<div><bdi>ね、</bdi>ね、</div> +<div><bdi>の、</bdi>の、</div> +<div><bdi>は、</bdi>は、</div> +<div><bdi>ひ、</bdi>ひ、</div> +<div><bdi>ふ、</bdi>ふ、</div> +<div><bdi>へ、</bdi>へ、</div> +<div><bdi>ほ、</bdi>ほ、</div> +<div><bdi>ま、</bdi>ま、</div> +<div><bdi>み、</bdi>み、</div> +<div><bdi>む、</bdi>む、</div> +<div><bdi>め、</bdi>め、</div> +<div><bdi>も、</bdi>も、</div> +<div><bdi>や、</bdi>や、</div> +<div><bdi>ゆ、</bdi>ゆ、</div> +<div><bdi>よ、</bdi>よ、</div> +<div><bdi>ら、</bdi>ら、</div> +<div><bdi>り、</bdi>り、</div> +<div><bdi>る、</bdi>る、</div> +<div><bdi>れ、</bdi>れ、</div> +<div><bdi>ろ、</bdi>ろ、</div> +<div><bdi>わ、</bdi>わ、</div> +<div><bdi>ゐ、</bdi>ゐ、</div> +<div><bdi>ゑ、</bdi>ゑ、</div> +<div><bdi>を、</bdi>を、</div> +<div><bdi>ん、</bdi>ん、</div> +</ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-030.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-030.html index 3acadc04b42..37bc6efe4c0 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-030.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-030.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-030-ref.html'> <meta name="assert" content="Setting list-style-type to hiragana will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: hiragana; } +ol { list-style-type: hiragana; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-031-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-031-ref.html new file mode 100644 index 00000000000..1cb332ab037 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-031-ref.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hiragana, extended</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to hiragana will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: hiragana; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='49'><div><bdi>ああ、</bdi>ああ、</div></ol> +<ol start='50'><div><bdi>あい、</bdi>あい、</div></ol> +<ol start='51'><div><bdi>あう、</bdi>あう、</div></ol> +<ol start='77'><div><bdi>あへ、</bdi>あへ、</div></ol> +<ol start='80'><div><bdi>あみ、</bdi>あみ、</div></ol> +<ol start='99'><div><bdi>いう、</bdi>いう、</div></ol> +<ol start='100'><div><bdi>いえ、</bdi>いえ、</div></ol> +<ol start='101'><div><bdi>いお、</bdi>いお、</div></ol> +<ol start='222'><div><bdi>えほ、</bdi>えほ、</div></ol> +<ol start='540'><div><bdi>さし、</bdi>さし、</div></ol> +<ol start='999'><div><bdi>とら、</bdi>とら、</div></ol> +<ol start='1000'><div><bdi>とり、</bdi>とり、</div></ol> +<ol start='1005'><div><bdi>とゐ、</bdi>とゐ、</div></ol> +<ol start='1060'><div><bdi>にえ、</bdi>にえ、</div></ol> +<ol start='1065'><div><bdi>にけ、</bdi>にけ、</div></ol> +<ol start='1800'><div><bdi>ゆね、</bdi>ゆね、</div></ol> +<ol start='1860'><div><bdi>よや、</bdi>よや、</div></ol> +<ol start='5865'><div><bdi>いはけ、</bdi>いはけ、</div></ol> +<ol start='7005'><div><bdi>うあゐ、</bdi>うあゐ、</div></ol> +<ol start='7800'><div><bdi>うつね、</bdi>うつね、</div></ol> +<ol start='7864'><div><bdi>うてり、</bdi>うてり、</div></ol> +<ol start='9999'><div><bdi>えたそ、</bdi>えたそ、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-031.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-031.html index 791a9d8cb5f..194517b774d 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-031.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-031.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-031-ref.html'> <meta name="assert" content="Setting list-style-type to hiragana will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: hiragana; } +ol { list-style-type: hiragana; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-032-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-032-ref.html new file mode 100644 index 00000000000..4db8a55c196 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-032-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>hiragana, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to hiragana will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: hiragana; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>あ、</bdi>あ、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-032.html b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-032.html index 066c221b114..929eaf9ec64 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-032.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/hiragana/css3-counter-styles-032.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-032-ref.html'> <meta name="assert" content="Setting list-style-type to hiragana will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: hiragana; } +ol { list-style-type: hiragana; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-047-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-047-ref.html new file mode 100644 index 00000000000..674dc561564 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-047-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-formal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to japanese-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>零、</bdi>零、</div></ol> +<ol start='1'><div><bdi>壱、</bdi>壱、</div></ol> +<ol start='2'><div><bdi>弐、</bdi>弐、</div></ol> +<ol start='3'><div><bdi>参、</bdi>参、</div></ol> +<ol start='4'><div><bdi>四、</bdi>四、</div></ol> +<ol start='5'><div><bdi>伍、</bdi>伍、</div></ol> +<ol start='6'><div><bdi>六、</bdi>六、</div></ol> +<ol start='7'><div><bdi>七、</bdi>七、</div></ol> +<ol start='8'><div><bdi>八、</bdi>八、</div></ol> +<ol start='9'><div><bdi>九、</bdi>九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-047.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-047.html index a31d819f6d6..0c35c7381d9 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-047.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-047.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-047-ref.html'> <meta name="assert" content="Setting list-style-type to japanese-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-formal; } +ol { list-style-type: japanese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-048-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-048-ref.html new file mode 100644 index 00000000000..a4dcae278e1 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-048-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-formal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to japanese-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>壱拾、</bdi>壱拾、</div></ol> +<ol start='11'><div><bdi>壱拾壱、</bdi>壱拾壱、</div></ol> +<ol start='12'><div><bdi>壱拾弐、</bdi>壱拾弐、</div></ol> +<ol start='43'><div><bdi>四拾参、</bdi>四拾参、</div></ol> +<ol start='77'><div><bdi>七拾七、</bdi>七拾七、</div></ol> +<ol start='80'><div><bdi>八拾、</bdi>八拾、</div></ol> +<ol start='99'><div><bdi>九拾九、</bdi>九拾九、</div></ol> +<ol start='100'><div><bdi>壱百、</bdi>壱百、</div></ol> +<ol start='101'><div><bdi>壱百壱、</bdi>壱百壱、</div></ol> +<ol start='222'><div><bdi>弐百弐拾弐、</bdi>弐百弐拾弐、</div></ol> +<ol start='540'><div><bdi>伍百四拾、</bdi>伍百四拾、</div></ol> +<ol start='999'><div><bdi>九百九拾九、</bdi>九百九拾九、</div></ol> +<ol start='1000'><div><bdi>壱阡、</bdi>壱阡、</div></ol> +<ol start='1005'><div><bdi>壱阡伍、</bdi>壱阡伍、</div></ol> +<ol start='1060'><div><bdi>壱阡六拾、</bdi>壱阡六拾、</div></ol> +<ol start='1065'><div><bdi>壱阡六拾伍、</bdi>壱阡六拾伍、</div></ol> +<ol start='1800'><div><bdi>壱阡八百、</bdi>壱阡八百、</div></ol> +<ol start='1860'><div><bdi>壱阡八百六拾、</bdi>壱阡八百六拾、</div></ol> +<ol start='1865'><div><bdi>壱阡八百六拾伍、</bdi>壱阡八百六拾伍、</div></ol> +<ol start='5865'><div><bdi>伍阡八百六拾伍、</bdi>伍阡八百六拾伍、</div></ol> +<ol start='7005'><div><bdi>七阡伍、</bdi>七阡伍、</div></ol> +<ol start='7800'><div><bdi>七阡八百、</bdi>七阡八百、</div></ol> +<ol start='7865'><div><bdi>七阡八百六拾伍、</bdi>七阡八百六拾伍、</div></ol> +<ol start='9999'><div><bdi>九阡九百九拾九、</bdi>九阡九百九拾九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-048.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-048.html index 120bfb03bfa..c8fa76acbe3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-048.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-048.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-048-ref.html'> <meta name="assert" content="Setting list-style-type to japanese-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-formal; } +ol { list-style-type: japanese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-049-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-049-ref.html new file mode 100644 index 00000000000..88e845d98a8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-049-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-formal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: japanese-formal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-formal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits and is NOT the same as the right side. Score as Partial if the columns of the 2nd and 3rd lines are the same (ie. fallback was used). In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>九阡九百九拾九、</bdi>九阡九百九拾九</div> +<div><bdi>一〇〇〇〇、</bdi>一〇〇〇〇</div> +<div><bdi>一〇〇〇一、</bdi>一〇〇〇一</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-049.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-049.html index 946b10c862d..a42bf1ae149 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-049.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-049.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-049-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: japanese-formal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-formal; } +ol { list-style-type: japanese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-050-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-050-ref.html new file mode 100644 index 00000000000..f81997e4515 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-050-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-formal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to japanese-formal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>マイナス壱拾壱、</bdi>マイナス壱拾壱、</div><div><bdi>マイナス壱拾、</bdi>マイナス壱拾、</div><div><bdi>マイナス九、</bdi>マイナス九、</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-050.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-050.html index d22ecdc1018..f157901cdaa 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-050.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-050.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-050-ref.html'> <meta name="assert" content="With list-style-type set to japanese-formal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-formal; } +ol { list-style-type: japanese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-051-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-051-ref.html new file mode 100644 index 00000000000..9208581de94 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-051-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-formal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to japanese-formal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>壱、</bdi>壱、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-051.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-051.html index 6838710c23a..baa3cf9792b 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-051.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-formal/css3-counter-styles-051.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-051-ref.html'> <meta name="assert" content="Setting list-style-type to japanese-formal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-formal; } +ol { list-style-type: japanese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-042-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-042-ref.html new file mode 100644 index 00000000000..34c7dcafc73 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-042-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-informal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to japanese-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>〇、</bdi>〇、</div></ol> +<ol start='1'><div><bdi>一、</bdi>一、</div></ol> +<ol start='2'><div><bdi>二、</bdi>二、</div></ol> +<ol start='3'><div><bdi>三、</bdi>三、</div></ol> +<ol start='4'><div><bdi>四、</bdi>四、</div></ol> +<ol start='5'><div><bdi>五、</bdi>五、</div></ol> +<ol start='6'><div><bdi>六、</bdi>六、</div></ol> +<ol start='7'><div><bdi>七、</bdi>七、</div></ol> +<ol start='8'><div><bdi>八、</bdi>八、</div></ol> +<ol start='9'><div><bdi>九、</bdi>九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-042.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-042.html index 2c5dcaec9fe..5e30b3fd65c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-042.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-042.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-042-ref.html'> <meta name="assert" content="Setting list-style-type to japanese-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-informal; } +ol { list-style-type: japanese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-043-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-043-ref.html new file mode 100644 index 00000000000..899f13fe25a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-043-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-informal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to japanese-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>十、</bdi>十、</div></ol> +<ol start='11'><div><bdi>十一、</bdi>十一、</div></ol> +<ol start='12'><div><bdi>十二、</bdi>十二、</div></ol> +<ol start='43'><div><bdi>四十三、</bdi>四十三、</div></ol> +<ol start='77'><div><bdi>七十七、</bdi>七十七、</div></ol> +<ol start='80'><div><bdi>八十、</bdi>八十、</div></ol> +<ol start='99'><div><bdi>九十九、</bdi>九十九、</div></ol> +<ol start='100'><div><bdi>百、</bdi>百、</div></ol> +<ol start='101'><div><bdi>百一、</bdi>百一、</div></ol> +<ol start='222'><div><bdi>二百二十二、</bdi>二百二十二、</div></ol> +<ol start='540'><div><bdi>五百四十、</bdi>五百四十、</div></ol> +<ol start='999'><div><bdi>九百九十九、</bdi>九百九十九、</div></ol> +<ol start='1000'><div><bdi>千、</bdi>千、</div></ol> +<ol start='1005'><div><bdi>千五、</bdi>千五、</div></ol> +<ol start='1060'><div><bdi>千六十、</bdi>千六十、</div></ol> +<ol start='1065'><div><bdi>千六十五、</bdi>千六十五、</div></ol> +<ol start='1800'><div><bdi>千八百、</bdi>千八百、</div></ol> +<ol start='1860'><div><bdi>千八百六十、</bdi>千八百六十、</div></ol> +<ol start='1865'><div><bdi>千八百六十五、</bdi>千八百六十五、</div></ol> +<ol start='5865'><div><bdi>五千八百六十五、</bdi>五千八百六十五、</div></ol> +<ol start='7005'><div><bdi>七千五、</bdi>七千五、</div></ol> +<ol start='7800'><div><bdi>七千八百、</bdi>七千八百、</div></ol> +<ol start='7865'><div><bdi>七千八百六十五、</bdi>七千八百六十五、</div></ol> +<ol start='9999'><div><bdi>九千九百九十九、</bdi>九千九百九十九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-043.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-043.html index b6df20c8301..e848de79478 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-043.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-043.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-043-ref.html'> <meta name="assert" content="Setting list-style-type to japanese-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-informal; } +ol { list-style-type: japanese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-044-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-044-ref.html new file mode 100644 index 00000000000..f26172abe31 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-044-ref.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-informal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: japanese-informal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +ol li { list-style-type: japanese-informal; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits and is NOT the same as the right side. Score as Partial if the columns of the 2nd and 3rd lines are the same (ie. fallback was used). In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>九千九百九十九、</bdi>九千九百九十九</div> +<div><bdi>一〇〇〇〇、</bdi>一〇〇〇〇</div> +<div><bdi>一〇〇〇一、</bdi>一〇〇〇一</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-044.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-044.html index 01af21c3399..f67af8c1923 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-044.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-044.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-044-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: japanese-informal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> ol li { list-style-type: japanese-informal; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-045-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-045-ref.html new file mode 100644 index 00000000000..20c37830a20 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-045-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-informal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to japanese-informal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>マイナス十一、</bdi>マイナス十一、</div><div><bdi>マイナス十、</bdi>マイナス十、</div><div><bdi>マイナス九、</bdi>マイナス九、</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-045.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-045.html index 570f2a07ed6..889294e7ff1 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-045.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-045.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-045-ref.html'> <meta name="assert" content="With list-style-type set to japanese-informal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-informal; } +ol { list-style-type: japanese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-046-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-046-ref.html new file mode 100644 index 00000000000..484271e0f91 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-046-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>japanese-informal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to japanese-informal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: japanese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>一、</bdi>一、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-046.html b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-046.html index 32f346bb0d7..d4502311203 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-046.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/japanese-informal/css3-counter-styles-046.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-046-ref.html'> <meta name="assert" content="Setting list-style-type to japanese-informal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: japanese-informal; } +ol { list-style-type: japanese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-128-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-128-ref.html new file mode 100644 index 00000000000..031e00ebb0a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-128-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>kannada, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:kannada produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: kannada; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>೧. </bdi>೧</div> +<div><bdi>೨. </bdi>೨</div> +<div><bdi>೩. </bdi>೩</div> +<div><bdi>೪. </bdi>೪</div> +<div><bdi>೫. </bdi>೫</div> +<div><bdi>೬. </bdi>೬</div> +<div><bdi>೭. </bdi>೭</div> +<div><bdi>೮. </bdi>೮</div> +<div><bdi>೯. </bdi>೯</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-128.html b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-128.html index b782cc8060b..e6183794c03 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-128.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-128.html @@ -5,13 +5,13 @@ <title>kannada, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-128-ref.html'> <meta name="assert" content="list-style-type:kannada produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: kannada; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-129-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-129-ref.html new file mode 100644 index 00000000000..900a7c1691b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-129-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>kannada, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: kannada produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: kannada; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>೧೦. </bdi>೧೦</div></ol> +<ol start="11"><div><bdi>೧೧. </bdi>೧೧</div></ol> +<ol start="12"><div><bdi>೧೨. </bdi>೧೨</div></ol> +<ol start="43"><div><bdi>೪೩. </bdi>೪೩</div></ol> +<ol start="77"><div><bdi>೭೭. </bdi>೭೭</div></ol> +<ol start="80"><div><bdi>೮೦. </bdi>೮೦</div></ol> +<ol start="99"><div><bdi>೯೯. </bdi>೯೯</div></ol> +<ol start="100"><div><bdi>೧೦೦. </bdi>೧೦೦</div></ol> +<ol start="101"><div><bdi>೧೦೧. </bdi>೧೦೧</div></ol> +<ol start="222"><div><bdi>೨೨೨. </bdi>೨೨೨</div></ol> +<ol start="540"><div><bdi>೫೪೦. </bdi>೫೪೦</div></ol> +<ol start="999"><div><bdi>೯೯೯. </bdi>೯೯೯</div></ol> +<ol start="1000"><div><bdi>೧೦೦೦. </bdi>೧೦೦೦</div></ol> +<ol start="1005"><div><bdi>೧೦೦೫. </bdi>೧೦೦೫</div></ol> +<ol start="1060"><div><bdi>೧೦೬೦. </bdi>೧೦೬೦</div></ol> +<ol start="1065"><div><bdi>೧೦೬೫. </bdi>೧೦೬೫</div></ol> +<ol start="1800"><div><bdi>೧೮೦೦. </bdi>೧೮೦೦</div></ol> +<ol start="1860"><div><bdi>೧೮೬೦. </bdi>೧೮೬೦</div></ol> +<ol start="1865"><div><bdi>೧೮೬೫. </bdi>೧೮೬೫</div></ol> +<ol start="5865"><div><bdi>೫೮೬೫. </bdi>೫೮೬೫</div></ol> +<ol start="7005"><div><bdi>೭೦೦೫. </bdi>೭೦೦೫</div></ol> +<ol start="7800"><div><bdi>೭೮೦೦. </bdi>೭೮೦೦</div></ol> +<ol start="7864"><div><bdi>೭೮೬೪. </bdi>೭೮೬೪</div></ol> +<ol start="9999"><div><bdi>೯೯೯೯. </bdi>೯೯೯೯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-129.html b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-129.html index 19c453039b9..902911542b0 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-129.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-129.html @@ -5,13 +5,13 @@ <title>kannada, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-129-ref.html'> <meta name="assert" content="list-style-type: kannada produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: kannada; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-130-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-130-ref.html new file mode 100644 index 00000000000..443f07b6c3c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-130-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>kannada, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: kannada produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: kannada; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>೧. </bdi>೧.</div> +<div><bdi>೨. </bdi>೨.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-130.html b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-130.html index 957423d8d92..8bd508c4cac 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-130.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/kannada/css3-counter-styles-130.html @@ -5,13 +5,13 @@ <title>kannada, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-130-ref.html'> <meta name="assert" content="list-style-type: kannada produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: kannada; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-039-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-039-ref.html new file mode 100644 index 00000000000..af0ca1a5adb --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-039-ref.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>katakana-iroha, simple</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to katakana-iroha will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: katakana-iroha; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol><div><bdi>イ、</bdi>イ、</div> +<div><bdi>ロ、</bdi>ロ、</div> +<div><bdi>ハ、</bdi>ハ、</div> +<div><bdi>ニ、</bdi>ニ、</div> +<div><bdi>ホ、</bdi>ホ、</div> +<div><bdi>ヘ、</bdi>ヘ、</div> +<div><bdi>ト、</bdi>ト、</div> +<div><bdi>チ、</bdi>チ、</div> +<div><bdi>リ、</bdi>リ、</div> +<div><bdi>ヌ、</bdi>ヌ、</div> +<div><bdi>ル、</bdi>ル、</div> +<div><bdi>ヲ、</bdi>ヲ、</div> +<div><bdi>ワ、</bdi>ワ、</div> +<div><bdi>カ、</bdi>カ、</div> +<div><bdi>ヨ、</bdi>ヨ、</div> +<div><bdi>タ、</bdi>タ、</div> +<div><bdi>レ、</bdi>レ、</div> +<div><bdi>ソ、</bdi>ソ、</div> +<div><bdi>ツ、</bdi>ツ、</div> +<div><bdi>ネ、</bdi>ネ、</div> +<div><bdi>ナ、</bdi>ナ、</div> +<div><bdi>ラ、</bdi>ラ、</div> +<div><bdi>ム、</bdi>ム、</div> +<div><bdi>ウ、</bdi>ウ、</div> +<div><bdi>ヰ、</bdi>ヰ、</div> +<div><bdi>ノ、</bdi>ノ、</div> +<div><bdi>オ、</bdi>オ、</div> +<div><bdi>ク、</bdi>ク、</div> +<div><bdi>ヤ、</bdi>ヤ、</div> +<div><bdi>マ、</bdi>マ、</div> +<div><bdi>ケ、</bdi>ケ、</div> +<div><bdi>フ、</bdi>フ、</div> +<div><bdi>コ、</bdi>コ、</div> +<div><bdi>エ、</bdi>エ、</div> +<div><bdi>テ、</bdi>テ、</div> +<div><bdi>ア、</bdi>ア、</div> +<div><bdi>サ、</bdi>サ、</div> +<div><bdi>キ、</bdi>キ、</div> +<div><bdi>ユ、</bdi>ユ、</div> +<div><bdi>メ、</bdi>メ、</div> +<div><bdi>ミ、</bdi>ミ、</div> +<div><bdi>シ、</bdi>シ、</div> +<div><bdi>ヱ、</bdi>ヱ、</div> +<div><bdi>ヒ、</bdi>ヒ、</div> +<div><bdi>モ、</bdi>モ、</div> +<div><bdi>セ、</bdi>セ、</div> +<div><bdi>ス、</bdi>ス、</div> +</ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-039.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-039.html index 6fd47073a3c..04c1a96d2ab 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-039.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-039.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-039-ref.html'> <meta name="assert" content="Setting list-style-type to katakana-iroha will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: katakana-iroha; } +ol { list-style-type: katakana-iroha; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-040-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-040-ref.html new file mode 100644 index 00000000000..056ea7cb67f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-040-ref.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>katakana-iroha, extended</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to katakana-iroha will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: katakana-iroha; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='48'><div><bdi>イイ、</bdi>イイ、</div></ol> +<ol start='77'><div><bdi>イマ、</bdi>イマ、</div></ol> +<ol start='80'><div><bdi>イコ、</bdi>イコ、</div></ol> +<ol start='99'><div><bdi>ロホ、</bdi>ロホ、</div></ol> +<ol start='100'><div><bdi>ロヘ、</bdi>ロヘ、</div></ol> +<ol start='101'><div><bdi>ロト、</bdi>ロト、</div></ol> +<ol start='222'><div><bdi>ニエ、</bdi>ニエ、</div></ol> +<ol start='540'><div><bdi>ルム、</bdi>ルム、</div></ol> +<ol start='999'><div><bdi>ナヲ、</bdi>ナヲ、</div></ol> +<ol start='1000'><div><bdi>ナワ、</bdi>ナワ、</div></ol> +<ol start='1005'><div><bdi>ナソ、</bdi>ナソ、</div></ol> +<ol start='1060'><div><bdi>ラノ、</bdi>ラノ、</div></ol> +<ol start='1065'><div><bdi>ラケ、</bdi>ラケ、</div></ol> +<ol start='1800'><div><bdi>キカ、</bdi>キカ、</div></ol> +<ol start='1860'><div><bdi>ユオ、</bdi>ユオ、</div></ol> +<ol start='5865'><div><bdi>ロマサ、</bdi>ロマサ、</div></ol> +<ol start='7005'><div><bdi>ハチロ、</bdi>ハチロ、</div></ol> +<ol start='7800'><div><bdi>ハウモ、</bdi>ハウモ、</div></ol> +<ol start='7864'><div><bdi>ハノヨ、</bdi>ハノヨ、</div></ol> +<ol start='9999'><div><bdi>ニウテ、</bdi>ニウテ、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-040.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-040.html index 5b12a0c4b9a..081e0afc3de 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-040.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-040.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-040-ref.html'> <meta name="assert" content="Setting list-style-type to katakana-iroha will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: katakana-iroha; } +ol { list-style-type: katakana-iroha; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-041-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-041-ref.html new file mode 100644 index 00000000000..558b932f7f8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-041-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>katakana-iroha, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to katakana-iroha will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: katakana-iroha; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>イ、</bdi>イ、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-041.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-041.html index f87354f2e8a..3eda2310ca0 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-041.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana-iroha/css3-counter-styles-041.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-041-ref.html'> <meta name="assert" content="Setting list-style-type to katakana-iroha will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: katakana-iroha; } +ol { list-style-type: katakana-iroha; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-036-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-036-ref.html new file mode 100644 index 00000000000..b18c730ad8f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-036-ref.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>katakana, simple</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to katakana will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: katakana; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol><div><bdi>ア、</bdi>ア、</div> +<div><bdi>イ、</bdi>イ、</div> +<div><bdi>ウ、</bdi>ウ、</div> +<div><bdi>エ、</bdi>エ、</div> +<div><bdi>オ、</bdi>オ、</div> +<div><bdi>カ、</bdi>カ、</div> +<div><bdi>キ、</bdi>キ、</div> +<div><bdi>ク、</bdi>ク、</div> +<div><bdi>ケ、</bdi>ケ、</div> +<div><bdi>コ、</bdi>コ、</div> +<div><bdi>サ、</bdi>サ、</div> +<div><bdi>シ、</bdi>シ、</div> +<div><bdi>ス、</bdi>ス、</div> +<div><bdi>セ、</bdi>セ、</div> +<div><bdi>ソ、</bdi>ソ、</div> +<div><bdi>タ、</bdi>タ、</div> +<div><bdi>チ、</bdi>チ、</div> +<div><bdi>ツ、</bdi>ツ、</div> +<div><bdi>テ、</bdi>テ、</div> +<div><bdi>ト、</bdi>ト、</div> +<div><bdi>ナ、</bdi>ナ、</div> +<div><bdi>ニ、</bdi>ニ、</div> +<div><bdi>ヌ、</bdi>ヌ、</div> +<div><bdi>ネ、</bdi>ネ、</div> +<div><bdi>ノ、</bdi>ノ、</div> +<div><bdi>ハ、</bdi>ハ、</div> +<div><bdi>ヒ、</bdi>ヒ、</div> +<div><bdi>フ、</bdi>フ、</div> +<div><bdi>ヘ、</bdi>ヘ、</div> +<div><bdi>ホ、</bdi>ホ、</div> +<div><bdi>マ、</bdi>マ、</div> +<div><bdi>ミ、</bdi>ミ、</div> +<div><bdi>ム、</bdi>ム、</div> +<div><bdi>メ、</bdi>メ、</div> +<div><bdi>モ、</bdi>モ、</div> +<div><bdi>ヤ、</bdi>ヤ、</div> +<div><bdi>ユ、</bdi>ユ、</div> +<div><bdi>ヨ、</bdi>ヨ、</div> +<div><bdi>ラ、</bdi>ラ、</div> +<div><bdi>リ、</bdi>リ、</div> +<div><bdi>ル、</bdi>ル、</div> +<div><bdi>レ、</bdi>レ、</div> +<div><bdi>ロ、</bdi>ロ、</div> +<div><bdi>ワ、</bdi>ワ、</div> +<div><bdi>ヰ、</bdi>ヰ、</div> +<div><bdi>ヱ、</bdi>ヱ、</div> +<div><bdi>ヲ、</bdi>ヲ、</div> +<div><bdi>ン、</bdi>ン、</div> +</ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-036.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-036.html index 4662dd710cf..0d955835f1c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-036.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-036.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-036-ref.html'> <meta name="assert" content="Setting list-style-type to katakana will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: katakana; } +ol { list-style-type: katakana; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-037-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-037-ref.html new file mode 100644 index 00000000000..2f851c2f5a2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-037-ref.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>katakana, extended</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to katakana will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: katakana; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='49'><div><bdi>アア、</bdi>アア、</div></ol> +<ol start='50'><div><bdi>アイ、</bdi>アイ、</div></ol> +<ol start='51'><div><bdi>アウ、</bdi>アウ、</div></ol> +<ol start='77'><div><bdi>アヘ、</bdi>アヘ、</div></ol> +<ol start='80'><div><bdi>アミ、</bdi>アミ、</div></ol> +<ol start='99'><div><bdi>イウ、</bdi>イウ、</div></ol> +<ol start='100'><div><bdi>イエ、</bdi>イエ、</div></ol> +<ol start='101'><div><bdi>イオ、</bdi>イオ、</div></ol> +<ol start='222'><div><bdi>エホ、</bdi>エホ、</div></ol> +<ol start='540'><div><bdi>サシ、</bdi>サシ、</div></ol> +<ol start='999'><div><bdi>トラ、</bdi>トラ、</div></ol> +<ol start='1000'><div><bdi>トリ、</bdi>トリ、</div></ol> +<ol start='1005'><div><bdi>トヰ、</bdi>トヰ、</div></ol> +<ol start='1060'><div><bdi>ニエ、</bdi>ニエ、</div></ol> +<ol start='1065'><div><bdi>ニケ、</bdi>ニケ、</div></ol> +<ol start='1800'><div><bdi>ユネ、</bdi>ユネ、</div></ol> +<ol start='1860'><div><bdi>ヨヤ、</bdi>ヨヤ、</div></ol> +<ol start='5865'><div><bdi>イハケ、</bdi>イハケ、</div></ol> +<ol start='7005'><div><bdi>ウアヰ、</bdi>ウアヰ、</div></ol> +<ol start='7800'><div><bdi>ウツネ、</bdi>ウツネ、</div></ol> +<ol start='7864'><div><bdi>ウテリ、</bdi>ウテリ、</div></ol> +<ol start='9999'><div><bdi>エタソ、</bdi>エタソ、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-037.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-037.html index cf3e07b05d8..3b6523e4c72 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-037.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-037.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-037-ref.html'> <meta name="assert" content="Setting list-style-type to katakana will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: katakana; } +ol { list-style-type: katakana; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-038-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-038-ref.html new file mode 100644 index 00000000000..83e0c07650c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-038-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>katakana, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to katakana will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: katakana; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>ア、</bdi>ア、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-038.html b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-038.html index cbbf779b165..a5736b6c295 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-038.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/katakana/css3-counter-styles-038.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-038-ref.html'> <meta name="assert" content="Setting list-style-type to katakana will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: katakana; } +ol { list-style-type: katakana; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-161-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-161-ref.html new file mode 100644 index 00000000000..f0baed877b9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-161-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>khmer, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: khmer produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: khmer; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>១. </bdi>១</div> +<div><bdi>២. </bdi>២</div> +<div><bdi>៣. </bdi>៣</div> +<div><bdi>៤. </bdi>៤</div> +<div><bdi>៥. </bdi>៥</div> +<div><bdi>៦. </bdi>៦</div> +<div><bdi>៧. </bdi>៧</div> +<div><bdi>៨. </bdi>៨</div> +<div><bdi>៩. </bdi>៩</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-161.html b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-161.html index c9fd66540ca..b486cd0738f 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-161.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-161.html @@ -5,13 +5,13 @@ <title>khmer, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-161-ref.html'> <meta name="assert" content="list-style-type: khmer produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: khmer; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-162-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-162-ref.html new file mode 100644 index 00000000000..91b8a1ef7cc --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-162-ref.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>khmer, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: khmer produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: khmer; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="11"><div><bdi>១១. </bdi>១១</div></ol> +<ol start="12"><div><bdi>១២. </bdi>១២</div></ol> +<ol start="43"><div><bdi>៤៣. </bdi>៤៣</div></ol> +<ol start="77"><div><bdi>៧៧. </bdi>៧៧</div></ol> +<ol start="80"><div><bdi>៨០. </bdi>៨០</div></ol> +<ol start="99"><div><bdi>៩៩. </bdi>៩៩</div></ol> +<ol start="100"><div><bdi>១០០. </bdi>១០០</div></ol> +<ol start="101"><div><bdi>១០១. </bdi>១០១</div></ol> +<ol start="222"><div><bdi>២២២. </bdi>២២២</div></ol> +<ol start="540"><div><bdi>៥៤០. </bdi>៥៤០</div></ol> +<ol start="999"><div><bdi>៩៩៩. </bdi>៩៩៩</div></ol> +<ol start="1000"><div><bdi>១០០០. </bdi>១០០០</div></ol> +<ol start="1005"><div><bdi>១០០៥. </bdi>១០០៥</div></ol> +<ol start="1060"><div><bdi>១០៦០. </bdi>១០៦០</div></ol> +<ol start="1065"><div><bdi>១០៦៥. </bdi>១០៦៥</div></ol> +<ol start="1800"><div><bdi>១៨០០. </bdi>១៨០០</div></ol> +<ol start="1860"><div><bdi>១៨៦០. </bdi>១៨៦០</div></ol> +<ol start="5865"><div><bdi>៥៨៦៥. </bdi>៥៨៦៥</div></ol> +<ol start="7005"><div><bdi>៧០០៥. </bdi>៧០០៥</div></ol> +<ol start="7800"><div><bdi>៧៨០០. </bdi>៧៨០០</div></ol> +<ol start="7864"><div><bdi>៧៨៦៤. </bdi>៧៨៦៤</div></ol> +<ol start="9999"><div><bdi>៩៩៩៩. </bdi>៩៩៩៩</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-162.html b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-162.html index 369fabb6183..bf60c05ff1f 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-162.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-162.html @@ -5,13 +5,13 @@ <title>khmer, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-162-ref.html'> <meta name="assert" content="list-style-type: khmer produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: khmer; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-163-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-163-ref.html new file mode 100644 index 00000000000..2fd104f5540 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-163-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>khmer, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: khmer produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: khmer; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>១. </bdi>១.</div> +<div><bdi>២. </bdi>២.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-163.html b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-163.html index 9d689d5c873..dcc1a138507 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-163.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/khmer/css3-counter-styles-163.html @@ -5,13 +5,13 @@ <title>khmer, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-163-ref.html'> <meta name="assert" content="list-style-type: khmer produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: khmer; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-052-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-052-ref.html new file mode 100644 index 00000000000..08e67923816 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-052-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hangul-formal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hangul-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hangul-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>영, </bdi>영,</div></ol> +<ol start='1'><div><bdi>일, </bdi>일,</div></ol> +<ol start='2'><div><bdi>이, </bdi>이,</div></ol> +<ol start='3'><div><bdi>삼, </bdi>삼,</div></ol> +<ol start='4'><div><bdi>사, </bdi>사,</div></ol> +<ol start='5'><div><bdi>오, </bdi>오,</div></ol> +<ol start='6'><div><bdi>육, </bdi>육,</div></ol> +<ol start='7'><div><bdi>칠, </bdi>칠,</div></ol> +<ol start='8'><div><bdi>팔, </bdi>팔,</div></ol> +<ol start='9'><div><bdi>구, </bdi>구,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-052.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-052.html index cb6a8cae2ee..ff557273b9a 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-052.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-052.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-052-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hangul-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hangul-formal; } +ol { list-style-type: korean-hangul-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-053-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-053-ref.html new file mode 100644 index 00000000000..3d429715bee --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-053-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hangul-formal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hangul-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hangul-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>일십, </bdi>일십,</div></ol> +<ol start='11'><div><bdi>일십일, </bdi>일십일,</div></ol> +<ol start='12'><div><bdi>일십이, </bdi>일십이,</div></ol> +<ol start='43'><div><bdi>사십삼, </bdi>사십삼,</div></ol> +<ol start='77'><div><bdi>칠십칠, </bdi>칠십칠,</div></ol> +<ol start='80'><div><bdi>팔십, </bdi>팔십,</div></ol> +<ol start='99'><div><bdi>구십구, </bdi>구십구,</div></ol> +<ol start='100'><div><bdi>일백, </bdi>일백,</div></ol> +<ol start='101'><div><bdi>일백일, </bdi>일백일,</div></ol> +<ol start='222'><div><bdi>이백이십이, </bdi>이백이십이,</div></ol> +<ol start='540'><div><bdi>오백사십, </bdi>오백사십,</div></ol> +<ol start='999'><div><bdi>구백구십구, </bdi>구백구십구,</div></ol> +<ol start='1000'><div><bdi>일천, </bdi>일천,</div></ol> +<ol start='1005'><div><bdi>일천오, </bdi>일천오,</div></ol> +<ol start='1060'><div><bdi>일천육십, </bdi>일천육십,</div></ol> +<ol start='1065'><div><bdi>일천육십오, </bdi>일천육십오,</div></ol> +<ol start='1800'><div><bdi>일천팔백, </bdi>일천팔백,</div></ol> +<ol start='1860'><div><bdi>일천팔백육십, </bdi>일천팔백육십,</div></ol> +<ol start='1865'><div><bdi>일천팔백육십오, </bdi>일천팔백육십오,</div></ol> +<ol start='5865'><div><bdi>오천팔백육십오, </bdi>오천팔백육십오,</div></ol> +<ol start='7005'><div><bdi>칠천오, </bdi>칠천오,</div></ol> +<ol start='7800'><div><bdi>칠천팔백, </bdi>칠천팔백,</div></ol> +<ol start='7865'><div><bdi>칠천팔백육십오, </bdi>칠천팔백육십오,</div></ol> +<ol start='9999'><div><bdi>구천구백구십구, </bdi>구천구백구십구,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-053.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-053.html index 70e007c3177..7d2ebdc88a9 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-053.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-053.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-053-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hangul-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hangul-formal; } +ol { list-style-type: korean-hangul-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-054-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-054-ref.html new file mode 100644 index 00000000000..9fd5f3c595b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-054-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hangul-formal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: korean-hangul-formal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hangul-formal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits. If it is decimal digits (ie. the fallback) score as Partial. In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>구천구백구십구, </bdi>구천구백구십구</div> +<div><bdi>10000, </bdi>10000.</div> +<div><bdi>10001, </bdi>10001.</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-054.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-054.html index b541d19bf30..9f08ead2a7c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-054.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-054.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-054-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: korean-hangul-formal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hangul-formal; } +ol { list-style-type: korean-hangul-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-055-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-055-ref.html new file mode 100644 index 00000000000..5159f00171b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-055-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hangul-formal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to korean-hangul-formal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hangul-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>마이너스 일십일, </bdi>마이너스 일십일,</div><div><bdi>마이너스 일십, </bdi>마이너스 일십,</div><div><bdi>마이너스 구, </bdi>마이너스 구,</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-055.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-055.html index 4a35e732c06..029c8a6f971 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-055.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-055.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-055-ref.html'> <meta name="assert" content="With list-style-type set to korean-hangul-formal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hangul-formal; } +ol { list-style-type: korean-hangul-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-056-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-056-ref.html new file mode 100644 index 00000000000..8fc78ae2cda --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-056-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hangul-formal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hangul-formal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hangul-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>일, </bdi>일,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-056.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-056.html index a8ab993e4ad..d86af1d6bb3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-056.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hangul-formal/css3-counter-styles-056.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-056-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hangul-formal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hangul-formal; } +ol { list-style-type: korean-hangul-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-062-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-062-ref.html new file mode 100644 index 00000000000..2ba86bd02e1 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-062-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-formal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hanja-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>零, </bdi>零,</div></ol> +<ol start='1'><div><bdi>壹, </bdi>壹,</div></ol> +<ol start='2'><div><bdi>貳, </bdi>貳,</div></ol> +<ol start='3'><div><bdi>參, </bdi>參,</div></ol> +<ol start='4'><div><bdi>四, </bdi>四,</div></ol> +<ol start='5'><div><bdi>五, </bdi>五,</div></ol> +<ol start='6'><div><bdi>六, </bdi>六,</div></ol> +<ol start='7'><div><bdi>七, </bdi>七,</div></ol> +<ol start='8'><div><bdi>八, </bdi>八,</div></ol> +<ol start='9'><div><bdi>九, </bdi>九,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-062.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-062.html index 40890e5e6a9..9be2ef33939 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-062.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-062.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-062-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hanja-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-formal; } +ol { list-style-type: korean-hanja-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-063-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-063-ref.html new file mode 100644 index 00000000000..9ac975a4118 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-063-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-formal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hanja-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>壹拾, </bdi>壹拾,</div></ol> +<ol start='11'><div><bdi>壹拾壹, </bdi>壹拾壹,</div></ol> +<ol start='12'><div><bdi>壹拾貳, </bdi>壹拾貳,</div></ol> +<ol start='43'><div><bdi>四拾參, </bdi>四拾參,</div></ol> +<ol start='77'><div><bdi>七拾七, </bdi>七拾七,</div></ol> +<ol start='80'><div><bdi>八拾, </bdi>八拾,</div></ol> +<ol start='99'><div><bdi>九拾九, </bdi>九拾九,</div></ol> +<ol start='100'><div><bdi>壹百, </bdi>壹百,</div></ol> +<ol start='101'><div><bdi>壹百壹, </bdi>壹百壹,</div></ol> +<ol start='222'><div><bdi>貳百貳拾貳, </bdi>貳百貳拾貳,</div></ol> +<ol start='540'><div><bdi>五百四拾, </bdi>五百四拾,</div></ol> +<ol start='999'><div><bdi>九百九拾九, </bdi>九百九拾九,</div></ol> +<ol start='1000'><div><bdi>壹仟, </bdi>壹仟,</div></ol> +<ol start='1005'><div><bdi>壹仟五, </bdi>壹仟五,</div></ol> +<ol start='1060'><div><bdi>壹仟六拾, </bdi>壹仟六拾,</div></ol> +<ol start='1065'><div><bdi>壹仟六拾五, </bdi>壹仟六拾五,</div></ol> +<ol start='1800'><div><bdi>壹仟八百, </bdi>壹仟八百,</div></ol> +<ol start='1860'><div><bdi>壹仟八百六拾, </bdi>壹仟八百六拾,</div></ol> +<ol start='1865'><div><bdi>壹仟八百六拾五, </bdi>壹仟八百六拾五,</div></ol> +<ol start='5865'><div><bdi>五仟八百六拾五, </bdi>五仟八百六拾五,</div></ol> +<ol start='7005'><div><bdi>七仟五, </bdi>七仟五,</div></ol> +<ol start='7800'><div><bdi>七仟八百, </bdi>七仟八百,</div></ol> +<ol start='7865'><div><bdi>七仟八百六拾五, </bdi>七仟八百六拾五,</div></ol> +<ol start='9999'><div><bdi>九仟九百九拾九, </bdi>九仟九百九拾九,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-063.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-063.html index 28482aee039..b58660d6990 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-063.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-063.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-063-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hanja-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-formal; } +ol { list-style-type: korean-hanja-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-064-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-064-ref.html new file mode 100644 index 00000000000..66648d19e5c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-064-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-formal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: korean-hanja-formal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-formal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits. If it is decimal digits (ie. the fallback) score as Partial. In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>九仟九百九拾九, </bdi>九仟九百九拾九</div> +<div><bdi>10000, </bdi>10000.</div> +<div><bdi>10001, </bdi>10001.</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-064.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-064.html index 8240e025df0..c8636d9ccce 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-064.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-064.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-064-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: korean-hanja-formal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-formal; } +ol { list-style-type: korean-hanja-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-065-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-065-ref.html new file mode 100644 index 00000000000..3c8b443d182 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-065-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-formal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to korean-hanja-formal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>마이너스 壹拾壹, </bdi>마이너스 壹拾壹,</div><div><bdi>마이너스 壹拾, </bdi>마이너스 壹拾,</div><div><bdi>마이너스 九, </bdi>마이너스 九,</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-065.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-065.html index e07b99acc42..b094d644b82 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-065.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-065.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-065-ref.html'> <meta name="assert" content="With list-style-type set to korean-hanja-formal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-formal; } +ol { list-style-type: korean-hanja-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-066-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-066-ref.html new file mode 100644 index 00000000000..d6f44ed86d2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-066-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-formal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hanja-formal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>壹, </bdi>壹,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-066.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-066.html index 7d12eca9824..2825c62d0a7 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-066.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-formal/css3-counter-styles-066.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-066-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hanja-formal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-formal; } +ol { list-style-type: korean-hanja-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-057-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-057-ref.html new file mode 100644 index 00000000000..ead591ebc32 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-057-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-informal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hanja-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>零, </bdi>零,</div></ol> +<ol start='1'><div><bdi>一, </bdi>一,</div></ol> +<ol start='2'><div><bdi>二, </bdi>二,</div></ol> +<ol start='3'><div><bdi>三, </bdi>三,</div></ol> +<ol start='4'><div><bdi>四, </bdi>四,</div></ol> +<ol start='5'><div><bdi>五, </bdi>五,</div></ol> +<ol start='6'><div><bdi>六, </bdi>六,</div></ol> +<ol start='7'><div><bdi>七, </bdi>七,</div></ol> +<ol start='8'><div><bdi>八, </bdi>八,</div></ol> +<ol start='9'><div><bdi>九, </bdi>九,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-057.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-057.html index 5634075c13c..d366198b9a8 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-057.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-057.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-057-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hanja-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-informal; } +ol { list-style-type: korean-hanja-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-058-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-058-ref.html new file mode 100644 index 00000000000..6d40686ab8e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-058-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-informal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hanja-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>十, </bdi>十,</div></ol> +<ol start='11'><div><bdi>十一, </bdi>十一,</div></ol> +<ol start='12'><div><bdi>十二, </bdi>十二,</div></ol> +<ol start='43'><div><bdi>四十三, </bdi>四十三,</div></ol> +<ol start='77'><div><bdi>七十七, </bdi>七十七,</div></ol> +<ol start='80'><div><bdi>八十, </bdi>八十,</div></ol> +<ol start='99'><div><bdi>九十九, </bdi>九十九,</div></ol> +<ol start='100'><div><bdi>百, </bdi>百,</div></ol> +<ol start='101'><div><bdi>百一, </bdi>百一,</div></ol> +<ol start='222'><div><bdi>二百二十二, </bdi>二百二十二,</div></ol> +<ol start='540'><div><bdi>五百四十, </bdi>五百四十,</div></ol> +<ol start='999'><div><bdi>九百九十九, </bdi>九百九十九,</div></ol> +<ol start='1000'><div><bdi>千, </bdi>千,</div></ol> +<ol start='1005'><div><bdi>千五, </bdi>千五,</div></ol> +<ol start='1060'><div><bdi>千六十, </bdi>千六十,</div></ol> +<ol start='1065'><div><bdi>千六十五, </bdi>千六十五,</div></ol> +<ol start='1800'><div><bdi>千八百, </bdi>千八百,</div></ol> +<ol start='1860'><div><bdi>千八百六十, </bdi>千八百六十,</div></ol> +<ol start='1865'><div><bdi>千八百六十五, </bdi>千八百六十五,</div></ol> +<ol start='5865'><div><bdi>五千八百六十五, </bdi>五千八百六十五,</div></ol> +<ol start='7005'><div><bdi>七千五, </bdi>七千五,</div></ol> +<ol start='7800'><div><bdi>七千八百, </bdi>七千八百,</div></ol> +<ol start='7865'><div><bdi>七千八百六十五, </bdi>七千八百六十五,</div></ol> +<ol start='9999'><div><bdi>九千九百九十九, </bdi>九千九百九十九,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-058.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-058.html index 47538198a51..fcac1ad3eb2 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-058.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-058.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-058-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hanja-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-informal; } +ol { list-style-type: korean-hanja-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-059-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-059-ref.html new file mode 100644 index 00000000000..c295420211e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-059-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-informal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: korean-hanja-informal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-informal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits. If it is decimal digits (ie. the fallback) score as Partial. In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>九千九百九十九, </bdi>九千九百九十九</div> +<div><bdi>10000, </bdi>10000.</div> +<div><bdi>10001, </bdi>10001.</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-059.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-059.html index 476ea29cdb0..c4c08d1a83b 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-059.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-059.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-059-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: korean-hanja-informal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-informal; } +ol { list-style-type: korean-hanja-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-060-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-060-ref.html new file mode 100644 index 00000000000..ca5accbcf15 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-060-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-informal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to korean-hanja-informal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>마이너스 十一, </bdi>마이너스 十一,</div><div><bdi>마이너스 十, </bdi>마이너스 十,</div><div><bdi>마이너스 九, </bdi>마이너스 九,</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-060.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-060.html index 2127ed7fbb2..8688ec98b24 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-060.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-060.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-060-ref.html'> <meta name="assert" content="With list-style-type set to korean-hanja-informal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-informal; } +ol { list-style-type: korean-hanja-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-061-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-061-ref.html new file mode 100644 index 00000000000..b6da540189a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-061-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>korean-hanja-informal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to korean-hanja-informal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: korean-hanja-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>一, </bdi>一,</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-061.html b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-061.html index 8ff24a7d70b..b223a837edb 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-061.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/korean-hanja-informal/css3-counter-styles-061.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-061-ref.html'> <meta name="assert" content="Setting list-style-type to korean-hanja-informal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: korean-hanja-informal; } +ol { list-style-type: korean-hanja-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-131-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-131-ref.html new file mode 100644 index 00000000000..7978103a9b9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-131-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lao, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:lao produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: lao; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>໑. </bdi>໑</div> +<div><bdi>໒. </bdi>໒</div> +<div><bdi>໓. </bdi>໓</div> +<div><bdi>໔. </bdi>໔</div> +<div><bdi>໕. </bdi>໕</div> +<div><bdi>໖. </bdi>໖</div> +<div><bdi>໗. </bdi>໗</div> +<div><bdi>໘. </bdi>໘</div> +<div><bdi>໙. </bdi>໙</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-131.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-131.html index 4a77df5b485..a06be2446a7 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-131.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-131.html @@ -5,13 +5,13 @@ <title>lao, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-131-ref.html'> <meta name="assert" content="list-style-type:lao produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: lao; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-132-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-132-ref.html new file mode 100644 index 00000000000..7ba7a9b7457 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-132-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lao, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lao produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: lao; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>໑໐. </bdi>໑໐</div></ol> +<ol start="11"><div><bdi>໑໑. </bdi>໑໑</div></ol> +<ol start="12"><div><bdi>໑໒. </bdi>໑໒</div></ol> +<ol start="43"><div><bdi>໔໓. </bdi>໔໓</div></ol> +<ol start="77"><div><bdi>໗໗. </bdi>໗໗</div></ol> +<ol start="80"><div><bdi>໘໐. </bdi>໘໐</div></ol> +<ol start="99"><div><bdi>໙໙. </bdi>໙໙</div></ol> +<ol start="100"><div><bdi>໑໐໐. </bdi>໑໐໐</div></ol> +<ol start="101"><div><bdi>໑໐໑. </bdi>໑໐໑</div></ol> +<ol start="222"><div><bdi>໒໒໒. </bdi>໒໒໒</div></ol> +<ol start="540"><div><bdi>໕໔໐. </bdi>໕໔໐</div></ol> +<ol start="999"><div><bdi>໙໙໙. </bdi>໙໙໙</div></ol> +<ol start="1000"><div><bdi>໑໐໐໐. </bdi>໑໐໐໐</div></ol> +<ol start="1005"><div><bdi>໑໐໐໕. </bdi>໑໐໐໕</div></ol> +<ol start="1060"><div><bdi>໑໐໖໐. </bdi>໑໐໖໐</div></ol> +<ol start="1065"><div><bdi>໑໐໖໕. </bdi>໑໐໖໕</div></ol> +<ol start="1800"><div><bdi>໑໘໐໐. </bdi>໑໘໐໐</div></ol> +<ol start="1860"><div><bdi>໑໘໖໐. </bdi>໑໘໖໐</div></ol> +<ol start="1865"><div><bdi>໑໘໖໕. </bdi>໑໘໖໕</div></ol> +<ol start="5865"><div><bdi>໕໘໖໕. </bdi>໕໘໖໕</div></ol> +<ol start="7005"><div><bdi>໗໐໐໕. </bdi>໗໐໐໕</div></ol> +<ol start="7800"><div><bdi>໗໘໐໐. </bdi>໗໘໐໐</div></ol> +<ol start="7864"><div><bdi>໗໘໖໔. </bdi>໗໘໖໔</div></ol> +<ol start="9999"><div><bdi>໙໙໙໙. </bdi>໙໙໙໙</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-132.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-132.html index bbe5107ba31..d8f24d6babb 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-132.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-132.html @@ -5,13 +5,13 @@ <title>lao, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-132-ref.html'> <meta name="assert" content="list-style-type: lao produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: lao; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-133-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-133-ref.html new file mode 100644 index 00000000000..aa65f960aaf --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-133-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lao, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lao produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: lao; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>໑. </bdi>໑.</div> +<div><bdi>໒. </bdi>໒.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-133.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-133.html index 3cabd354e48..41bf1fb8614 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-133.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lao/css3-counter-styles-133.html @@ -5,13 +5,13 @@ <title>lao, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-133-ref.html'> <meta name="assert" content="list-style-type: lao produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: lao; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-111-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-111-ref.html new file mode 100644 index 00000000000..8c76e057de2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-111-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-armenian, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lower-armenian produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: lower-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>ա. </bdi>ա</div> +<div><bdi>բ. </bdi>բ</div> +<div><bdi>գ. </bdi>գ</div> +<div><bdi>դ. </bdi>դ</div> +<div><bdi>ե. </bdi>ե</div> +<div><bdi>զ. </bdi>զ</div> +<div><bdi>է. </bdi>է</div> +<div><bdi>ը. </bdi>ը</div> +<div><bdi>թ. </bdi>թ</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-111.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-111.html index 88d0395736d..075d8cc98f8 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-111.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-111.html @@ -5,13 +5,13 @@ <title>lower-armenian, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-111-ref.html'> <meta name="assert" content="list-style-type: lower-armenian produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: lower-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-112-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-112-ref.html new file mode 100644 index 00000000000..3299f0b365a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-112-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-armenian, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lower-armenian produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: lower-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>ժ. </bdi>ժ</div></ol> +<ol start="11"><div><bdi>ժա. </bdi>ժա</div></ol> +<ol start="12"><div><bdi>ժբ. </bdi>ժբ</div></ol> +<ol start="43"><div><bdi>խգ. </bdi>խգ</div></ol> +<ol start="77"><div><bdi>հէ. </bdi>հէ</div></ol> +<ol start="80"><div><bdi>ձ. </bdi>ձ</div></ol> +<ol start="99"><div><bdi>ղթ. </bdi>ղթ</div></ol> +<ol start="100"><div><bdi>ճ. </bdi>ճ</div></ol> +<ol start="101"><div><bdi>ճա. </bdi>ճա</div></ol> +<ol start="222"><div><bdi>միբ. </bdi>միբ</div></ol> +<ol start="540"><div><bdi>շխ. </bdi>շխ</div></ol> +<ol start="999"><div><bdi>ջղթ. </bdi>ջղթ</div></ol> +<ol start="1000"><div><bdi>ռ. </bdi>ռ</div></ol> +<ol start="1005"><div><bdi>ռե. </bdi>ռե</div></ol> +<ol start="1060"><div><bdi>ռկ. </bdi>ռկ</div></ol> +<ol start="1065"><div><bdi>ռկե. </bdi>ռկե</div></ol> +<ol start="1800"><div><bdi>ռպ. </bdi>ռպ</div></ol> +<ol start="1860"><div><bdi>ռպկ. </bdi>ռպկ</div></ol> +<ol start="1865"><div><bdi>ռպկե. </bdi>ռպկե</div></ol> +<ol start="5865"><div><bdi>րպկե. </bdi>րպկե</div></ol> +<ol start="7005"><div><bdi>ւե. </bdi>ւե</div></ol> +<ol start="7800"><div><bdi>ւպ. </bdi>ւպ</div></ol> +<ol start="7865"><div><bdi>ւպկե. </bdi>ւպկե</div></ol> +<ol start="9999"><div><bdi>քջղթ. </bdi>քջղթ</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-112.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-112.html index d4239e0a0e4..4f078c37d3b 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-112.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-112.html @@ -5,13 +5,13 @@ <title>lower-armenian, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-112-ref.html'> <meta name="assert" content="list-style-type: lower-armenian produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: lower-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-114-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-114-ref.html new file mode 100644 index 00000000000..be41198f54a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-114-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-armenian, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lower-armenian produces numbers in the fallback counter style above the limit per the spec."> +<style type='text/css'> +ol li { list-style-type: lower-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='9999'> +<div><bdi>քջղթ. </bdi>քջղթ</div> +<div><bdi>10000. </bdi>10000</div> +<div><bdi>10001. </bdi>10001</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-114.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-114.html index 8f1cb8c1e96..b6a1888fb11 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-114.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-114.html @@ -5,13 +5,13 @@ <title>lower-armenian, outside range</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-114-ref.html'> <meta name="assert" content="list-style-type: lower-armenian produces numbers in the fallback counter style above the limit per the spec."> <style type='text/css'> ol li { list-style-type: lower-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-115-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-115-ref.html new file mode 100644 index 00000000000..7901ddb329a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-115-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-armenian, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lower-armenian produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: lower-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>ա. </bdi>ա.</div> +<div><bdi>բ. </bdi>բ.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-115.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-115.html index 08afc18c3b8..0d8cafb810d 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-115.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-armenian/css3-counter-styles-115.html @@ -5,13 +5,13 @@ <title>lower-armenian, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-115-ref.html'> <meta name="assert" content="list-style-type: lower-armenian produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: lower-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-027-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-027-ref.html new file mode 100644 index 00000000000..404eab6248f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-027-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-greek, simple</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to lower-greek will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: lower-greek; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol><div><bdi>α. </bdi>α.</div> +<div><bdi>β. </bdi>β.</div> +<div><bdi>γ. </bdi>γ.</div> +<div><bdi>δ. </bdi>δ.</div> +<div><bdi>ε. </bdi>ε.</div> +<div><bdi>ζ. </bdi>ζ.</div> +<div><bdi>η. </bdi>η.</div> +<div><bdi>θ. </bdi>θ.</div> +<div><bdi>ι. </bdi>ι.</div> +<div><bdi>κ. </bdi>κ.</div> +<div><bdi>λ. </bdi>λ.</div> +<div><bdi>μ. </bdi>μ.</div> +<div><bdi>ν. </bdi>ν.</div> +<div><bdi>ξ. </bdi>ξ.</div> +<div><bdi>ο. </bdi>ο.</div> +<div><bdi>π. </bdi>π.</div> +<div><bdi>ρ. </bdi>ρ.</div> +<div><bdi>σ. </bdi>σ.</div> +<div><bdi>τ. </bdi>τ.</div> +<div><bdi>υ. </bdi>υ.</div> +<div><bdi>φ. </bdi>φ.</div> +<div><bdi>χ. </bdi>χ.</div> +<div><bdi>ψ. </bdi>ψ.</div> +<div><bdi>ω. </bdi>ω.</div> +</ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-027.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-027.html index 875db78e44e..d76e28f64fc 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-027.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-027.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-027-ref.html'> <meta name="assert" content="Setting list-style-type to lower-greek will produce list numbering for the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: lower-greek; } +ol { list-style-type: lower-greek; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-028-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-028-ref.html new file mode 100644 index 00000000000..01024b7ada6 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-028-ref.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-greek, extended</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to lower-greek will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: lower-greek; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='25'><div><bdi>αα. </bdi>αα.</div></ol> +<ol start='26'><div><bdi>αβ. </bdi>αβ.</div></ol> +<ol start='43'><div><bdi>ατ. </bdi>ατ.</div></ol> +<ol start='77'><div><bdi>γε. </bdi>γε.</div></ol> +<ol start='80'><div><bdi>γθ. </bdi>γθ.</div></ol> +<ol start='99'><div><bdi>δγ. </bdi>δγ.</div></ol> +<ol start='100'><div><bdi>δδ. </bdi>δδ.</div></ol> +<ol start='101'><div><bdi>δε. </bdi>δε.</div></ol> +<ol start='222'><div><bdi>ιζ. </bdi>ιζ.</div></ol> +<ol start='540'><div><bdi>χμ. </bdi>χμ.</div></ol> +<ol start='999'><div><bdi>αρο. </bdi>αρο.</div></ol> +<ol start='1000'><div><bdi>αρπ. </bdi>αρπ.</div></ol> +<ol start='1005'><div><bdi>αρφ. </bdi>αρφ.</div></ol> +<ol start='1060'><div><bdi>αυδ. </bdi>αυδ.</div></ol> +<ol start='1065'><div><bdi>αυι. </bdi>αυι.</div></ol> +<ol start='1800'><div><bdi>γβω. </bdi>γβω.</div></ol> +<ol start='1860'><div><bdi>γεμ. </bdi>γεμ.</div></ol> +<ol start='5865'><div><bdi>κδι. </bdi>κδι.</div></ol> +<ol start='7005'><div><bdi>μγφ. </bdi>μγφ.</div></ol> +<ol start='7800'><div><bdi>νμω. </bdi>νμω.</div></ol> +<ol start='7864'><div><bdi>νοπ. </bdi>νοπ.</div></ol> +<ol start='9999'><div><bdi>ρθο. </bdi>ρθο.</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-028.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-028.html index d66c3c31761..3ae4ad59b3b 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-028.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-028.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-028-ref.html'> <meta name="assert" content="Setting list-style-type to lower-greek will produce list numbering after the basic alphabet as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: lower-greek; } +ol { list-style-type: lower-greek; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-029-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-029-ref.html new file mode 100644 index 00000000000..22387394d78 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-029-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-greek, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to lower-greek will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: lower-greek; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>α. </bdi>α.</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-029.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-029.html index a4b4f405f8a..ac4caaf49d3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-029.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-greek/css3-counter-styles-029.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-alphabetic'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-029-ref.html'> <meta name="assert" content="Setting list-style-type to lower-greek will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: lower-greek; } +ol { list-style-type: lower-greek; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-019-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-019-ref.html new file mode 100644 index 00000000000..7c88a2b3041 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-019-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-roman, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: lower-roman produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: lower-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"><ol> +<div><bdi>i. </bdi>i</div> +<div><bdi>ii. </bdi>ii</div> +<div><bdi>iii. </bdi>iii</div> +<div><bdi>iv. </bdi>iv</div> +<div><bdi>v. </bdi>v</div> +<div><bdi>vi. </bdi>vi</div> +<div><bdi>vii. </bdi>vii</div> +<div><bdi>viii. </bdi>viii</div> +<div><bdi>ix. </bdi>ix</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-019.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-019.html index d377e176c10..86bae7acbbc 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-019.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-019.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-019-ref.html'> <meta name="assert" content="list-style: lower-roman produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: lower-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020-ref.html new file mode 100644 index 00000000000..1d5e4149cbf --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020-ref.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-roman, 10-3999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: lower-roman produces numbers after 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: lower-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>x. </bdi>x</div></ol> +<ol start='11'><div><bdi>xi. </bdi>xi</div></ol> +<ol start='12'><div><bdi>xii. </bdi>xii</div></ol> +<ol start='43'><div><bdi>xliii. </bdi>xliii</div></ol> +<ol start='77'><div><bdi>lxxvii. </bdi>lxxvii</div></ol> +<ol start='80'><div><bdi>lxxx. </bdi>lxxx</div></ol> +<ol start='99'><div><bdi>xcix. </bdi>xcix</div></ol> +<ol start='100'><div><bdi>c. </bdi>c</div></ol> +<ol start='101'><div><bdi>ci. </bdi>ci</div></ol> +<ol start='222'><div><bdi>ccxxii. </bdi>ccxxii</div></ol> +<ol start='540'><div><bdi>dxl. </bdi>dxl</div></ol> +<ol start='999'><div><bdi>cmxcix. </bdi>cmxcix</div></ol> +<ol start='1000'><div><bdi>m. </bdi>m</div></ol> +<ol start='1005'><div><bdi>mv. </bdi>mv</div></ol> +<ol start='1060'><div><bdi>mlx. </bdi>mlx</div></ol> +<ol start='1065'><div><bdi>mlxv. </bdi>mlxv</div></ol> +<ol start='1800'><div><bdi>mdccc. </bdi>mdccc</div></ol> +<ol start='1860'><div><bdi>mdccclx. </bdi>mdccclx</div></ol> +<ol start='1865'><div><bdi>mdccclxv. </bdi>mdccclxv</div></ol> +<ol start='2555'><div><bdi>mmdlv. </bdi>mmdlv</div></ol> +<ol start='3000'><div><bdi>mmm. </bdi>mmm</div></ol> +<ol start='3999'><div><bdi>mmmcmxcix. </bdi>mmmcmxcix</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020.html index 48e39184406..db434a4177b 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-020-ref.html'> <meta name="assert" content="list-style: lower-roman produces numbers after 9 items per the spec."> <style type='text/css'> ol li { list-style-type: lower-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020a-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020a-ref.html new file mode 100644 index 00000000000..2276b22917d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020a-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-roman, 3000-3999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='author' title='Chris Lilley' href='mailto:chris@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to lower-roman will produce list of up to 9 items in the range range: 1 to 3999."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: lower-roman; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='3000'><div><bdi>mmm. </bdi>mmm.</div></ol> +<ol start='3555'><div><bdi>mmmdlv. </bdi>mmmdlv.</div></ol> +<ol start='3998'><div><bdi>mmmcmxcviii. </bdi>mmmcmxcviii.</div></ol> +<ol start='3999'><div><bdi>mmmcmxcix. </bdi>mmmcmxcix.</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020a.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020a.html index 17dda54f002..94482bc14cd 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020a.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020a.html @@ -7,12 +7,12 @@ <link rel='author' title='Chris Lilley' href='mailto:chris@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-020a-ref.html'> <meta name="assert" content="Setting list-style-type to lower-roman will produce list of up to 9 items in the range range: 1 to 3999."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: lower-roman; } +ol li { list-style-type: lower-roman; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020b-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020b-ref.html new file mode 100644 index 00000000000..975fa186d10 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020b-ref.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-roman, straddling range, 3000-4001</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='author' title='Chris Lilley' href='mailto:chris@w3.org'> +<link rel="reviewer" title="Tab Atkins" href="mailto:jackalmage@gmail.com" /> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#descdef-counter-style-range'> +<meta name='flags' content='font'> +<meta name="assert" content=" If a counter style is used to represent a counter value outside of its ranges, the counter style instead drops down to its fallback counter style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: lower-roman; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='3000'><div><bdi>mmm. </bdi>mmm.</div></ol> +<ol start='3555'><div><bdi>mmmdlv. </bdi>mmmdlv.</div></ol> +<ol start='3998'><div><bdi>mmmcmxcviii. </bdi>mmmcmxcviii.</div></ol> +<ol start='3999'><div><bdi>mmmcmxcix. </bdi>mmmcmxcix.</div></ol> +<ol start='4001'><div><bdi>4001. </bdi>4001.</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020b.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020b.html index db24763de38..34025cc92f2 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020b.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-020b.html @@ -7,12 +7,12 @@ <link rel='author' title='Chris Lilley' href='mailto:chris@w3.org'> <link rel="reviewer" title="Tab Atkins" href="mailto:jackalmage@gmail.com" /> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#descdef-counter-style-range'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-020b-ref.html'> <meta name="assert" content=" If a counter style is used to represent a counter value outside of its ranges, the counter style instead drops down to its fallback counter style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: lower-roman; } +ol li { list-style-type: lower-roman; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-021-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-021-ref.html new file mode 100644 index 00000000000..90fbeb7e72f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-021-ref.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-roman, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lower-roman produces numbers in the fallback counter style above the limit per the spec"> +<style type='text/css'> +ol li { list-style-type: lower-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"><ol start='3999'> +<div><bdi>mmmcmxcix. </bdi>mmmcmxcix</div> +<div><bdi>4000. </bdi>4000</div></ol> +<ol start='4001'><div><bdi>4001. </bdi>4001</div> +<div><bdi>4002. </bdi>4002</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-021.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-021.html index 794c3c9fbd6..30c4c9981dd 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-021.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-021.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-021-ref.html'> <meta name="assert" content="list-style-type: lower-roman produces numbers in the fallback counter style above the limit per the spec"> <style type='text/css'> ol li { list-style-type: lower-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-022-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-022-ref.html new file mode 100644 index 00000000000..f2e6b0f7d11 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-022-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>lower-roman, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: lower-roman produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: lower-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class="test"><ol> +<div><bdi>i. </bdi>i.</div> +<div><bdi>ii. </bdi>ii.</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-022.html b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-022.html index 29c4a8c8956..6bd53de981c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-022.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/lower-roman/css3-counter-styles-022.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-022-ref.html'> <meta name="assert" content="list-style-type: lower-roman produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: lower-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-134-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-134-ref.html new file mode 100644 index 00000000000..0014b15c807 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-134-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>malayalam, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:malayalam produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: malayalam; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>൧. </bdi>൧</div> +<div><bdi>൨. </bdi>൨</div> +<div><bdi>൩. </bdi>൩</div> +<div><bdi>൪. </bdi>൪</div> +<div><bdi>൫. </bdi>൫</div> +<div><bdi>൬. </bdi>൬</div> +<div><bdi>൭. </bdi>൭</div> +<div><bdi>൮. </bdi>൮</div> +<div><bdi>൯. </bdi>൯</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-134.html b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-134.html index 0254b57b4ee..9698be73e6d 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-134.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-134.html @@ -5,13 +5,13 @@ <title>malayalam, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-134-ref.html'> <meta name="assert" content="list-style-type:malayalam produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: malayalam; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-135-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-135-ref.html new file mode 100644 index 00000000000..1b4081ed1e9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-135-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>malayalam, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: malayalam produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: malayalam; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>൧൦. </bdi>൧൦</div></ol> +<ol start="11"><div><bdi>൧൧. </bdi>൧൧</div></ol> +<ol start="12"><div><bdi>൧൨. </bdi>൧൨</div></ol> +<ol start="43"><div><bdi>൪൩. </bdi>൪൩</div></ol> +<ol start="77"><div><bdi>൭൭. </bdi>൭൭</div></ol> +<ol start="80"><div><bdi>൮൦. </bdi>൮൦</div></ol> +<ol start="99"><div><bdi>൯൯. </bdi>൯൯</div></ol> +<ol start="100"><div><bdi>൧൦൦. </bdi>൧൦൦</div></ol> +<ol start="101"><div><bdi>൧൦൧. </bdi>൧൦൧</div></ol> +<ol start="222"><div><bdi>൨൨൨. </bdi>൨൨൨</div></ol> +<ol start="540"><div><bdi>൫൪൦. </bdi>൫൪൦</div></ol> +<ol start="999"><div><bdi>൯൯൯. </bdi>൯൯൯</div></ol> +<ol start="1000"><div><bdi>൧൦൦൦. </bdi>൧൦൦൦</div></ol> +<ol start="1005"><div><bdi>൧൦൦൫. </bdi>൧൦൦൫</div></ol> +<ol start="1060"><div><bdi>൧൦൬൦. </bdi>൧൦൬൦</div></ol> +<ol start="1065"><div><bdi>൧൦൬൫. </bdi>൧൦൬൫</div></ol> +<ol start="1800"><div><bdi>൧൮൦൦. </bdi>൧൮൦൦</div></ol> +<ol start="1860"><div><bdi>൧൮൬൦. </bdi>൧൮൬൦</div></ol> +<ol start="1865"><div><bdi>൧൮൬൫. </bdi>൧൮൬൫</div></ol> +<ol start="5865"><div><bdi>൫൮൬൫. </bdi>൫൮൬൫</div></ol> +<ol start="7005"><div><bdi>൭൦൦൫. </bdi>൭൦൦൫</div></ol> +<ol start="7800"><div><bdi>൭൮൦൦. </bdi>൭൮൦൦</div></ol> +<ol start="7864"><div><bdi>൭൮൬൪. </bdi>൭൮൬൪</div></ol> +<ol start="9999"><div><bdi>൯൯൯൯. </bdi>൯൯൯൯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-135.html b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-135.html index 167cfc1d52e..79a4bc092a4 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-135.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-135.html @@ -5,13 +5,13 @@ <title>malayalam, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-135-ref.html'> <meta name="assert" content="list-style-type: malayalam produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: malayalam; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-136-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-136-ref.html new file mode 100644 index 00000000000..434a29eb9f0 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-136-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>malayalam, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: malayalam produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: malayalam; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>൧. </bdi>൧.</div> +<div><bdi>൨. </bdi>൨.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-136.html b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-136.html index 643b404ea61..b79af7b2997 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-136.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/malayalam/css3-counter-styles-136.html @@ -5,13 +5,13 @@ <title>malayalam, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-136-ref.html'> <meta name="assert" content="list-style-type: malayalam produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: malayalam; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-137-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-137-ref.html new file mode 100644 index 00000000000..8d5998aede0 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-137-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>mongolian, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:mongolian produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: mongolian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>᠑. </bdi>᠑</div> +<div><bdi>᠒. </bdi>᠒</div> +<div><bdi>᠓. </bdi>᠓</div> +<div><bdi>᠔. </bdi>᠔</div> +<div><bdi>᠕. </bdi>᠕</div> +<div><bdi>᠖. </bdi>᠖</div> +<div><bdi>᠗. </bdi>᠗</div> +<div><bdi>᠘. </bdi>᠘</div> +<div><bdi>᠙. </bdi>᠙</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-137.html b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-137.html index 1ffb05ec27a..109250c3006 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-137.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-137.html @@ -5,13 +5,13 @@ <title>mongolian, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-137-ref.html'> <meta name="assert" content="list-style-type:mongolian produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: mongolian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-138-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-138-ref.html new file mode 100644 index 00000000000..7438fb54290 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-138-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>mongolian, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: mongolian produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: mongolian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>᠑᠐. </bdi>᠑᠐</div></ol> +<ol start="11"><div><bdi>᠑᠑. </bdi>᠑᠑</div></ol> +<ol start="12"><div><bdi>᠑᠒. </bdi>᠑᠒</div></ol> +<ol start="43"><div><bdi>᠔᠓. </bdi>᠔᠓</div></ol> +<ol start="77"><div><bdi>᠗᠗. </bdi>᠗᠗</div></ol> +<ol start="80"><div><bdi>᠘᠐. </bdi>᠘᠐</div></ol> +<ol start="99"><div><bdi>᠙᠙. </bdi>᠙᠙</div></ol> +<ol start="100"><div><bdi>᠑᠐᠐. </bdi>᠑᠐᠐</div></ol> +<ol start="101"><div><bdi>᠑᠐᠑. </bdi>᠑᠐᠑</div></ol> +<ol start="222"><div><bdi>᠒᠒᠒. </bdi>᠒᠒᠒</div></ol> +<ol start="540"><div><bdi>᠕᠔᠐. </bdi>᠕᠔᠐</div></ol> +<ol start="999"><div><bdi>᠙᠙᠙. </bdi>᠙᠙᠙</div></ol> +<ol start="1000"><div><bdi>᠑᠐᠐᠐. </bdi>᠑᠐᠐᠐</div></ol> +<ol start="1005"><div><bdi>᠑᠐᠐᠕. </bdi>᠑᠐᠐᠕</div></ol> +<ol start="1060"><div><bdi>᠑᠐᠖᠐. </bdi>᠑᠐᠖᠐</div></ol> +<ol start="1065"><div><bdi>᠑᠐᠖᠕. </bdi>᠑᠐᠖᠕</div></ol> +<ol start="1800"><div><bdi>᠑᠘᠐᠐. </bdi>᠑᠘᠐᠐</div></ol> +<ol start="1860"><div><bdi>᠑᠘᠖᠐. </bdi>᠑᠘᠖᠐</div></ol> +<ol start="1865"><div><bdi>᠑᠘᠖᠕. </bdi>᠑᠘᠖᠕</div></ol> +<ol start="5865"><div><bdi>᠕᠘᠖᠕. </bdi>᠕᠘᠖᠕</div></ol> +<ol start="7005"><div><bdi>᠗᠐᠐᠕. </bdi>᠗᠐᠐᠕</div></ol> +<ol start="7800"><div><bdi>᠗᠘᠐᠐. </bdi>᠗᠘᠐᠐</div></ol> +<ol start="7864"><div><bdi>᠗᠘᠖᠔. </bdi>᠗᠘᠖᠔</div></ol> +<ol start="9999"><div><bdi>᠙᠙᠙᠙. </bdi>᠙᠙᠙᠙</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-138.html b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-138.html index 4e45748f046..bf6e1fc5ad6 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-138.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-138.html @@ -5,13 +5,13 @@ <title>mongolian, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-138-ref.html'> <meta name="assert" content="list-style-type: mongolian produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: mongolian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-139-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-139-ref.html new file mode 100644 index 00000000000..ef75d78e801 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-139-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>mongolian, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: mongolian produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: mongolian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>᠑. </bdi>᠑.</div> +<div><bdi>᠒. </bdi>᠒.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-139.html b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-139.html index 435760c571d..923142ac070 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-139.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/mongolian/css3-counter-styles-139.html @@ -5,13 +5,13 @@ <title>mongolian, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-139-ref.html'> <meta name="assert" content="list-style-type: mongolian produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: mongolian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-140-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-140-ref.html new file mode 100644 index 00000000000..b9e9c0dfb69 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-140-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>myanmar, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:myanmar produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: myanmar; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>၁. </bdi>၁</div> +<div><bdi>၂. </bdi>၂</div> +<div><bdi>၃. </bdi>၃</div> +<div><bdi>၄. </bdi>၄</div> +<div><bdi>၅. </bdi>၅</div> +<div><bdi>၆. </bdi>၆</div> +<div><bdi>၇. </bdi>၇</div> +<div><bdi>၈. </bdi>၈</div> +<div><bdi>၉. </bdi>၉</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-140.html b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-140.html index 2b11c681b52..45441745fb7 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-140.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-140.html @@ -5,13 +5,13 @@ <title>myanmar, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-140-ref.html'> <meta name="assert" content="list-style-type:myanmar produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: myanmar; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-141-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-141-ref.html new file mode 100644 index 00000000000..b02434dbc58 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-141-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>myanmar, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: myanmar produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: myanmar; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>၁၀. </bdi>၁၀</div></ol> +<ol start="11"><div><bdi>၁၁. </bdi>၁၁</div></ol> +<ol start="12"><div><bdi>၁၂. </bdi>၁၂</div></ol> +<ol start="43"><div><bdi>၄၃. </bdi>၄၃</div></ol> +<ol start="77"><div><bdi>၇၇. </bdi>၇၇</div></ol> +<ol start="80"><div><bdi>၈၀. </bdi>၈၀</div></ol> +<ol start="99"><div><bdi>၉၉. </bdi>၉၉</div></ol> +<ol start="100"><div><bdi>၁၀၀. </bdi>၁၀၀</div></ol> +<ol start="101"><div><bdi>၁၀၁. </bdi>၁၀၁</div></ol> +<ol start="222"><div><bdi>၂၂၂. </bdi>၂၂၂</div></ol> +<ol start="540"><div><bdi>၅၄၀. </bdi>၅၄၀</div></ol> +<ol start="999"><div><bdi>၉၉၉. </bdi>၉၉၉</div></ol> +<ol start="1000"><div><bdi>၁၀၀၀. </bdi>၁၀၀၀</div></ol> +<ol start="1005"><div><bdi>၁၀၀၅. </bdi>၁၀၀၅</div></ol> +<ol start="1060"><div><bdi>၁၀၆၀. </bdi>၁၀၆၀</div></ol> +<ol start="1065"><div><bdi>၁၀၆၅. </bdi>၁၀၆၅</div></ol> +<ol start="1800"><div><bdi>၁၈၀၀. </bdi>၁၈၀၀</div></ol> +<ol start="1860"><div><bdi>၁၈၆၀. </bdi>၁၈၆၀</div></ol> +<ol start="1865"><div><bdi>၁၈၆၅. </bdi>၁၈၆၅</div></ol> +<ol start="5865"><div><bdi>၅၈၆၅. </bdi>၅၈၆၅</div></ol> +<ol start="7005"><div><bdi>၇၀၀၅. </bdi>၇၀၀၅</div></ol> +<ol start="7800"><div><bdi>၇၈၀၀. </bdi>၇၈၀၀</div></ol> +<ol start="7864"><div><bdi>၇၈၆၄. </bdi>၇၈၆၄</div></ol> +<ol start="9999"><div><bdi>၉၉၉၉. </bdi>၉၉၉၉</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-141.html b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-141.html index 112cc1abc54..30e8f1a94db 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-141.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-141.html @@ -5,13 +5,13 @@ <title>myanmar, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-141-ref.html'> <meta name="assert" content="list-style-type: myanmar produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: myanmar; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-142-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-142-ref.html new file mode 100644 index 00000000000..29b26da0e39 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-142-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>myanmar, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: myanmar produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: myanmar; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>၁. </bdi>၁.</div> +<div><bdi>၂. </bdi>၂.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-142.html b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-142.html index 22f6a303702..b279441869f 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-142.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/myanmar/css3-counter-styles-142.html @@ -5,13 +5,13 @@ <title>myanmar, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-142-ref.html'> <meta name="assert" content="list-style-type: myanmar produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: myanmar; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-143-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-143-ref.html new file mode 100644 index 00000000000..493f7cf8af7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-143-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>oriya, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:oriya produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: oriya; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>୧. </bdi>୧</div> +<div><bdi>୨. </bdi>୨</div> +<div><bdi>୩. </bdi>୩</div> +<div><bdi>୪. </bdi>୪</div> +<div><bdi>୫. </bdi>୫</div> +<div><bdi>୬. </bdi>୬</div> +<div><bdi>୭. </bdi>୭</div> +<div><bdi>୮. </bdi>୮</div> +<div><bdi>୯. </bdi>୯</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-143.html b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-143.html index 279742b5f18..c66805bdf1b 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-143.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-143.html @@ -5,13 +5,13 @@ <title>oriya, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-143-ref.html'> <meta name="assert" content="list-style-type:oriya produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: oriya; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-144-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-144-ref.html new file mode 100644 index 00000000000..c9d97c2abfa --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-144-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>oriya, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: oriya produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: oriya; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>୧୦. </bdi>୧୦</div></ol> +<ol start="11"><div><bdi>୧୧. </bdi>୧୧</div></ol> +<ol start="12"><div><bdi>୧୨. </bdi>୧୨</div></ol> +<ol start="43"><div><bdi>୪୩. </bdi>୪୩</div></ol> +<ol start="77"><div><bdi>୭୭. </bdi>୭୭</div></ol> +<ol start="80"><div><bdi>୮୦. </bdi>୮୦</div></ol> +<ol start="99"><div><bdi>୯୯. </bdi>୯୯</div></ol> +<ol start="100"><div><bdi>୧୦୦. </bdi>୧୦୦</div></ol> +<ol start="101"><div><bdi>୧୦୧. </bdi>୧୦୧</div></ol> +<ol start="222"><div><bdi>୨୨୨. </bdi>୨୨୨</div></ol> +<ol start="540"><div><bdi>୫୪୦. </bdi>୫୪୦</div></ol> +<ol start="999"><div><bdi>୯୯୯. </bdi>୯୯୯</div></ol> +<ol start="1000"><div><bdi>୧୦୦୦. </bdi>୧୦୦୦</div></ol> +<ol start="1005"><div><bdi>୧୦୦୫. </bdi>୧୦୦୫</div></ol> +<ol start="1060"><div><bdi>୧୦୬୦. </bdi>୧୦୬୦</div></ol> +<ol start="1065"><div><bdi>୧୦୬୫. </bdi>୧୦୬୫</div></ol> +<ol start="1800"><div><bdi>୧୮୦୦. </bdi>୧୮୦୦</div></ol> +<ol start="1860"><div><bdi>୧୮୬୦. </bdi>୧୮୬୦</div></ol> +<ol start="1865"><div><bdi>୧୮୬୫. </bdi>୧୮୬୫</div></ol> +<ol start="5865"><div><bdi>୫୮୬୫. </bdi>୫୮୬୫</div></ol> +<ol start="7005"><div><bdi>୭୦୦୫. </bdi>୭୦୦୫</div></ol> +<ol start="7800"><div><bdi>୭୮୦୦. </bdi>୭୮୦୦</div></ol> +<ol start="7864"><div><bdi>୭୮୬୪. </bdi>୭୮୬୪</div></ol> +<ol start="9999"><div><bdi>୯୯୯୯. </bdi>୯୯୯୯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-144.html b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-144.html index ec42bab0a86..28b79d505fd 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-144.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-144.html @@ -5,13 +5,13 @@ <title>oriya, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-144-ref.html'> <meta name="assert" content="list-style-type: oriya produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: oriya; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-145-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-145-ref.html new file mode 100644 index 00000000000..39d81f791b9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-145-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>oriya, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: oriya produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: oriya; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>୧. </bdi>୧.</div> +<div><bdi>୨. </bdi>୨.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-145.html b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-145.html index 3280343b939..07883894141 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-145.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/oriya/css3-counter-styles-145.html @@ -5,13 +5,13 @@ <title>oriya, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-145-ref.html'> <meta name="assert" content="list-style-type: oriya produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: oriya; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-104-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-104-ref.html new file mode 100644 index 00000000000..c1dac7e7187 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-104-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>persian, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: persian produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: persian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class='test'> +<ol> +<div><bdi>۱. </bdi>۱</div> +<div><bdi>۲. </bdi>۲</div> +<div><bdi>۳. </bdi>۳</div> +<div><bdi>۴. </bdi>۴</div> +<div><bdi>۵. </bdi>۵</div> +<div><bdi>۶. </bdi>۶</div> +<div><bdi>۷. </bdi>۷</div> +<div><bdi>۸. </bdi>۸</div> +<div><bdi>۹. </bdi>۹</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-104.html b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-104.html index 9886e5cf76f..b79a35d336d 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-104.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-104.html @@ -5,13 +5,13 @@ <title>persian, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-104-ref.html'> <meta name="assert" content="list-style: persian produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: persian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-105-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-105-ref.html new file mode 100644 index 00000000000..5adc1cf43a5 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-105-ref.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>persian, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: persian produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: persian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class='test'> +<ol start='10'> +<div><bdi>۱۰. </bdi>۱۰</div> +<div><bdi>۱۱. </bdi>۱۱</div> +<div><bdi>۱۲. </bdi>۱۲</div> +</ol> +<ol start='43'> +<div><bdi>۴۳. </bdi>۴۳</div> +</ol> +<ol start='77'> +<div><bdi>۷۷. </bdi>۷۷</div> +</ol> +<ol start='80'> +<div><bdi>۸۰. </bdi>۸۰</div> +</ol> +<ol start='99'> +<div><bdi>۹۹. </bdi>۹۹</div> +<div><bdi>۱۰۰. </bdi>۱۰۰</div> +<div><bdi>۱۰۱. </bdi>۱۰۱</div> +</ol> +<ol start='222'> +<div><bdi>۲۲۲. </bdi>۲۲۲</div> +</ol> +<ol start='540'> +<div><bdi>۵۴۰. </bdi>۵۴۰</div> +</ol> +<ol start='999'> +<div><bdi>۹۹۹. </bdi>۹۹۹</div> +<div><bdi>۱۰۰۰. </bdi>۱۰۰۰</div> +</ol> +<ol start='1005'> +<div><bdi>۱۰۰۵. </bdi>۱۰۰۵</div> +</ol> +<ol start='1060'> +<div><bdi>۱۰۶۰. </bdi>۱۰۶۰</div> +</ol> +<ol start='1065'> +<div><bdi>۱۰۶۵. </bdi>۱۰۶۵</div> +</ol> +<ol start='1800'> +<div><bdi>۱۸۰۰. </bdi>۱۸۰۰</div> +</ol> +<ol start='1860'> +<div><bdi>۱۸۶۰. </bdi>۱۸۶۰</div> +</ol> +<ol start='1865'> +<div><bdi>۱۸۶۵. </bdi>۱۸۶۵</div> +</ol> +<ol start='5865'> +<div><bdi>۵۸۶۵. </bdi>۵۸۶۵</div> +</ol> +<ol start='7005'> +<div><bdi>۷۰۰۵. </bdi>۷۰۰۵</div> +</ol> +<ol start='7800'> +<div><bdi>۷۸۰۰ . </bdi>۷۸۰۰ </div> +</ol> +<ol start='7864'> +<div><bdi>۷۸۶۴. </bdi>۷۸۶۴</div> +</ol> +<ol start='9999'> +<div><bdi>۹۹۹۹. </bdi>۹۹۹۹</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-105.html b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-105.html index 6a0dcf7c9ac..db0eb25cc21 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-105.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-105.html @@ -5,13 +5,13 @@ <title>persian, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-105-ref.html'> <meta name="assert" content="list-style-type: persian produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: persian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-106-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-106-ref.html new file mode 100644 index 00000000000..57102175a4c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-106-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>persian, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: persian produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: persian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>۱. </bdi>۱.</div> +<div><bdi>۲. </bdi>۲.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-106.html b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-106.html index 2b9edde718e..9a93e2ef63e 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-106.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/persian/css3-counter-styles-106.html @@ -5,13 +5,13 @@ <title>persian, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-106-ref.html'> <meta name="assert" content="list-style-type: persian produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: persian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-076-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-076-ref.html new file mode 100644 index 00000000000..eb6bedffdb3 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-076-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-formal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to simp-chinese-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>零、</bdi>零、</div></ol> +<ol start='1'><div><bdi>壹、</bdi>壹、</div></ol> +<ol start='2'><div><bdi>贰、</bdi>贰、</div></ol> +<ol start='3'><div><bdi>叁、</bdi>叁、</div></ol> +<ol start='4'><div><bdi>肆、</bdi>肆、</div></ol> +<ol start='5'><div><bdi>伍、</bdi>伍、</div></ol> +<ol start='6'><div><bdi>陆、</bdi>陆、</div></ol> +<ol start='7'><div><bdi>柒、</bdi>柒、</div></ol> +<ol start='8'><div><bdi>捌、</bdi>捌、</div></ol> +<ol start='9'><div><bdi>玖、</bdi>玖、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-076.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-076.html index f7db75ded21..b6256340978 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-076.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-076.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-076-ref.html'> <meta name="assert" content="Setting list-style-type to simp-chinese-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-formal; } +ol { list-style-type: simp-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-077-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-077-ref.html new file mode 100644 index 00000000000..8082fcdfff8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-077-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-formal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to simp-chinese-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>壹拾、</bdi>壹拾、</div></ol> +<ol start='11'><div><bdi>壹拾壹、</bdi>壹拾壹、</div></ol> +<ol start='12'><div><bdi>壹拾贰、</bdi>壹拾贰、</div></ol> +<ol start='43'><div><bdi>肆拾叁、</bdi>肆拾叁、</div></ol> +<ol start='77'><div><bdi>柒拾柒、</bdi>柒拾柒、</div></ol> +<ol start='80'><div><bdi>捌拾、</bdi>捌拾、</div></ol> +<ol start='99'><div><bdi>玖拾玖、</bdi>玖拾玖、</div></ol> +<ol start='100'><div><bdi>壹佰、</bdi>壹佰、</div></ol> +<ol start='101'><div><bdi>壹佰零壹、</bdi>壹佰零壹、</div></ol> +<ol start='222'><div><bdi>贰佰贰拾贰、</bdi>贰佰贰拾贰、</div></ol> +<ol start='540'><div><bdi>伍佰肆拾、</bdi>伍佰肆拾、</div></ol> +<ol start='999'><div><bdi>玖佰玖拾玖、</bdi>玖佰玖拾玖、</div></ol> +<ol start='1000'><div><bdi>壹仟、</bdi>壹仟、</div></ol> +<ol start='1005'><div><bdi>壹仟零伍、</bdi>壹仟零伍、</div></ol> +<ol start='1060'><div><bdi>壹仟零陆拾、</bdi>壹仟零陆拾、</div></ol> +<ol start='1065'><div><bdi>壹仟零陆拾伍、</bdi>壹仟零陆拾伍、</div></ol> +<ol start='1800'><div><bdi>壹仟捌佰、</bdi>壹仟捌佰、</div></ol> +<ol start='1860'><div><bdi>壹仟捌佰陆拾、</bdi>壹仟捌佰陆拾、</div></ol> +<ol start='1865'><div><bdi>壹仟捌佰陆拾伍、</bdi>壹仟捌佰陆拾伍、</div></ol> +<ol start='5865'><div><bdi>伍仟捌佰陆拾伍、</bdi>伍仟捌佰陆拾伍、</div></ol> +<ol start='7005'><div><bdi>柒仟零伍、</bdi>柒仟零伍、</div></ol> +<ol start='7800'><div><bdi>柒仟捌佰、</bdi>柒仟捌佰、</div></ol> +<ol start='7865'><div><bdi>柒仟捌佰陆拾伍、</bdi>柒仟捌佰陆拾伍、</div></ol> +<ol start='9999'><div><bdi>玖仟玖佰玖拾玖、</bdi>玖仟玖佰玖拾玖、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-077.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-077.html index d84025423c8..7e34e2e8902 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-077.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-077.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-077-ref.html'> <meta name="assert" content="Setting list-style-type to simp-chinese-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-formal; } +ol { list-style-type: simp-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-078-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-078-ref.html new file mode 100644 index 00000000000..7a6a673fe0e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-078-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-formal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: simp-chinese-formal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits and is NOT the same as the right side. Score as Partial if the columns of the 2nd and 3rd lines are the same (ie. fallback was used). In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>玖仟玖佰玖拾玖、</bdi>玖仟玖佰玖拾玖</div> +<div><bdi>一〇〇〇〇、</bdi>一〇〇〇〇</div> +<div><bdi>一〇〇〇一、</bdi>一〇〇〇一</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-078.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-078.html index b7b650455ac..9476d7f9736 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-078.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-078.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-078-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: simp-chinese-formal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-formal; } +ol { list-style-type: simp-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-079-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-079-ref.html new file mode 100644 index 00000000000..9354d0bb4ca --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-079-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-formal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to simp-chinese-formal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>负壹拾壹、</bdi>负壹拾壹、</div><div><bdi>负壹拾、</bdi>负壹拾、</div><div><bdi>负玖、</bdi>负玖、</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-079.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-079.html index 3c74989e5f2..284412d1ad3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-079.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-079.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-079-ref.html'> <meta name="assert" content="With list-style-type set to simp-chinese-formal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-formal; } +ol { list-style-type: simp-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-080-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-080-ref.html new file mode 100644 index 00000000000..d50246ec28f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-080-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-formal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to simp-chinese-formal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>壹、</bdi>壹、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-080.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-080.html index 958f44290b0..f8385225669 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-080.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-formal/css3-counter-styles-080.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-080-ref.html'> <meta name="assert" content="Setting list-style-type to simp-chinese-formal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-formal; } +ol { list-style-type: simp-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-071-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-071-ref.html new file mode 100644 index 00000000000..0db9f16174d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-071-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-informal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to simp-chinese-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>零、</bdi>零、</div></ol> +<ol start='1'><div><bdi>一、</bdi>一、</div></ol> +<ol start='2'><div><bdi>二、</bdi>二、</div></ol> +<ol start='3'><div><bdi>三、</bdi>三、</div></ol> +<ol start='4'><div><bdi>四、</bdi>四、</div></ol> +<ol start='5'><div><bdi>五、</bdi>五、</div></ol> +<ol start='6'><div><bdi>六、</bdi>六、</div></ol> +<ol start='7'><div><bdi>七、</bdi>七、</div></ol> +<ol start='8'><div><bdi>八、</bdi>八、</div></ol> +<ol start='9'><div><bdi>九、</bdi>九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-071.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-071.html index 6e11ecd0dc1..8daf67c76a4 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-071.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-071.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-071-ref.html'> <meta name="assert" content="Setting list-style-type to simp-chinese-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-informal; } +ol { list-style-type: simp-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-072-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-072-ref.html new file mode 100644 index 00000000000..4d93a54444f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-072-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-informal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to simp-chinese-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>十、</bdi>十、</div></ol> +<ol start='11'><div><bdi>十一、</bdi>十一、</div></ol> +<ol start='12'><div><bdi>十二、</bdi>十二、</div></ol> +<ol start='43'><div><bdi>四十三、</bdi>四十三、</div></ol> +<ol start='77'><div><bdi>七十七、</bdi>七十七、</div></ol> +<ol start='80'><div><bdi>八十、</bdi>八十、</div></ol> +<ol start='99'><div><bdi>九十九、</bdi>九十九、</div></ol> +<ol start='100'><div><bdi>一百、</bdi>一百、</div></ol> +<ol start='101'><div><bdi>一百零一、</bdi>一百零一、</div></ol> +<ol start='222'><div><bdi>二百二十二、</bdi>二百二十二、</div></ol> +<ol start='540'><div><bdi>五百四十、</bdi>五百四十、</div></ol> +<ol start='999'><div><bdi>九百九十九、</bdi>九百九十九、</div></ol> +<ol start='1000'><div><bdi>一千、</bdi>一千、</div></ol> +<ol start='1005'><div><bdi>一千零五、</bdi>一千零五、</div></ol> +<ol start='1060'><div><bdi>一千零六十、</bdi>一千零六十、</div></ol> +<ol start='1065'><div><bdi>一千零六十五、</bdi>一千零六十五、</div></ol> +<ol start='1800'><div><bdi>一千八百、</bdi>一千八百、</div></ol> +<ol start='1860'><div><bdi>一千八百六十、</bdi>一千八百六十、</div></ol> +<ol start='1865'><div><bdi>一千八百六十五、</bdi>一千八百六十五、</div></ol> +<ol start='5865'><div><bdi>五千八百六十五、</bdi>五千八百六十五、</div></ol> +<ol start='7005'><div><bdi>七千零五、</bdi>七千零五、</div></ol> +<ol start='7800'><div><bdi>七千八百、</bdi>七千八百、</div></ol> +<ol start='7865'><div><bdi>七千八百六十五、</bdi>七千八百六十五、</div></ol> +<ol start='9999'><div><bdi>九千九百九十九、</bdi>九千九百九十九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-072.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-072.html index 3af6151f6c2..cfb05badfcf 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-072.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-072.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-072-ref.html'> <meta name="assert" content="Setting list-style-type to simp-chinese-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-informal; } +ol { list-style-type: simp-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-073-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-073-ref.html new file mode 100644 index 00000000000..30ad3f30367 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-073-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-informal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: simp-chinese-informal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits and is NOT the same as the right side. Score as Partial if the columns of the 2nd and 3rd lines are the same (ie. fallback was used). In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>九千九百九十九、</bdi>九千九百九十九</div> +<div><bdi>一〇〇〇〇、</bdi>一〇〇〇〇</div> +<div><bdi>一〇〇〇一、</bdi>一〇〇〇一</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-073.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-073.html index 3e520e455bb..22936652e75 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-073.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-073.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-073-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: simp-chinese-informal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-informal; } +ol { list-style-type: simp-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-074-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-074-ref.html new file mode 100644 index 00000000000..fca43ca5d36 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-074-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-informal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to simp-chinese-informal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>负十一、</bdi>负十一、</div><div><bdi>负十、</bdi>负十、</div><div><bdi>负九、</bdi>负九、</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-074.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-074.html index f32a521319f..4dd91d03fa8 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-074.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-074.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-074-ref.html'> <meta name="assert" content="With list-style-type set to simp-chinese-informal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-informal; } +ol { list-style-type: simp-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-075-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-075-ref.html new file mode 100644 index 00000000000..feb92c81ab8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-075-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>simp-chinese-informal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to simp-chinese-informal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: simp-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>一、</bdi>一、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-075.html b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-075.html index bb7807e41fa..8d7450cd134 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-075.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/simp-chinese-informal/css3-counter-styles-075.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-075-ref.html'> <meta name="assert" content="Setting list-style-type to simp-chinese-informal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: simp-chinese-informal; } +ol { list-style-type: simp-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-146-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-146-ref.html new file mode 100644 index 00000000000..975ccff103f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-146-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>tamil, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:tamil produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: tamil; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>௧. </bdi>௧</div> +<div><bdi>௨. </bdi>௨</div> +<div><bdi>௩. </bdi>௩</div> +<div><bdi>௪. </bdi>௪</div> +<div><bdi>௫. </bdi>௫</div> +<div><bdi>௬. </bdi>௬</div> +<div><bdi>௭. </bdi>௭</div> +<div><bdi>௮. </bdi>௮</div> +<div><bdi>௯. </bdi>௯</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-146.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-146.html index d0476babc7f..74fbb919850 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-146.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-146.html @@ -5,13 +5,13 @@ <title>tamil, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-146-ref.html'> <meta name="assert" content="list-style-type:tamil produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: tamil; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-147-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-147-ref.html new file mode 100644 index 00000000000..5ad6a9960e0 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-147-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>tamil, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: tamil produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: tamil; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>௧௦. </bdi>௧௦</div></ol> +<ol start="11"><div><bdi>௧௧. </bdi>௧௧</div></ol> +<ol start="12"><div><bdi>௧௨. </bdi>௧௨</div></ol> +<ol start="43"><div><bdi>௪௩. </bdi>௪௩</div></ol> +<ol start="77"><div><bdi>௭௭. </bdi>௭௭</div></ol> +<ol start="80"><div><bdi>௮௦. </bdi>௮௦</div></ol> +<ol start="99"><div><bdi>௯௯. </bdi>௯௯</div></ol> +<ol start="100"><div><bdi>௧௦௦. </bdi>௧௦௦</div></ol> +<ol start="101"><div><bdi>௧௦௧. </bdi>௧௦௧</div></ol> +<ol start="222"><div><bdi>௨௨௨. </bdi>௨௨௨</div></ol> +<ol start="540"><div><bdi>௫௪௦. </bdi>௫௪௦</div></ol> +<ol start="999"><div><bdi>௯௯௯. </bdi>௯௯௯</div></ol> +<ol start="1000"><div><bdi>௧௦௦௦. </bdi>௧௦௦௦</div></ol> +<ol start="1005"><div><bdi>௧௦௦௫. </bdi>௧௦௦௫</div></ol> +<ol start="1060"><div><bdi>௧௦௬௦. </bdi>௧௦௬௦</div></ol> +<ol start="1065"><div><bdi>௧௦௬௫. </bdi>௧௦௬௫</div></ol> +<ol start="1800"><div><bdi>௧௮௦௦. </bdi>௧௮௦௦</div></ol> +<ol start="1860"><div><bdi>௧௮௬௦. </bdi>௧௮௬௦</div></ol> +<ol start="1865"><div><bdi>௧௮௬௫. </bdi>௧௮௬௫</div></ol> +<ol start="5865"><div><bdi>௫௮௬௫. </bdi>௫௮௬௫</div></ol> +<ol start="7005"><div><bdi>௭௦௦௫. </bdi>௭௦௦௫</div></ol> +<ol start="7800"><div><bdi>௭௮௦௦. </bdi>௭௮௦௦</div></ol> +<ol start="7864"><div><bdi>௭௮௬௪. </bdi>௭௮௬௪</div></ol> +<ol start="9999"><div><bdi>௯௯௯௯. </bdi>௯௯௯௯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-147.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-147.html index 4f5bfac3014..9a48a832989 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-147.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-147.html @@ -5,13 +5,13 @@ <title>tamil, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-147-ref.html'> <meta name="assert" content="list-style-type: tamil produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: tamil; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-148-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-148-ref.html new file mode 100644 index 00000000000..8f6a3718c66 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-148-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>tamil, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: tamil produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: tamil; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>௧. </bdi>௧.</div> +<div><bdi>௨. </bdi>௨.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-148.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-148.html index 948aa839b34..e149cb06bd7 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-148.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tamil/css3-counter-styles-148.html @@ -5,13 +5,13 @@ <title>tamil, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-148-ref.html'> <meta name="assert" content="list-style-type: tamil produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: tamil; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-149-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-149-ref.html new file mode 100644 index 00000000000..0121bd6f565 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-149-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>telugu, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:telugu produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: telugu; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>౧. </bdi>౧</div> +<div><bdi>౨. </bdi>౨</div> +<div><bdi>౩. </bdi>౩</div> +<div><bdi>౪. </bdi>౪</div> +<div><bdi>౫. </bdi>౫</div> +<div><bdi>౬. </bdi>౬</div> +<div><bdi>౭. </bdi>౭</div> +<div><bdi>౮. </bdi>౮</div> +<div><bdi>౯. </bdi>౯</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-149.html b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-149.html index 7bc24912043..b7008b78099 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-149.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-149.html @@ -5,13 +5,13 @@ <title>telugu, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-149-ref.html'> <meta name="assert" content="list-style-type:telugu produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: telugu; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-150-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-150-ref.html new file mode 100644 index 00000000000..7c717bda99c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-150-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>telugu, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: telugu produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: telugu; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>౧౦. </bdi>౧౦</div></ol> +<ol start="11"><div><bdi>౧౧. </bdi>౧౧</div></ol> +<ol start="12"><div><bdi>౧౨. </bdi>౧౨</div></ol> +<ol start="43"><div><bdi>౪౩. </bdi>౪౩</div></ol> +<ol start="77"><div><bdi>౭౭. </bdi>౭౭</div></ol> +<ol start="80"><div><bdi>౮౦. </bdi>౮౦</div></ol> +<ol start="99"><div><bdi>౯౯. </bdi>౯౯</div></ol> +<ol start="100"><div><bdi>౧౦౦. </bdi>౧౦౦</div></ol> +<ol start="101"><div><bdi>౧౦౧. </bdi>౧౦౧</div></ol> +<ol start="222"><div><bdi>౨౨౨. </bdi>౨౨౨</div></ol> +<ol start="540"><div><bdi>౫౪౦. </bdi>౫౪౦</div></ol> +<ol start="999"><div><bdi>౯౯౯. </bdi>౯౯౯</div></ol> +<ol start="1000"><div><bdi>౧౦౦౦. </bdi>౧౦౦౦</div></ol> +<ol start="1005"><div><bdi>౧౦౦౫. </bdi>౧౦౦౫</div></ol> +<ol start="1060"><div><bdi>౧౦౬౦. </bdi>౧౦౬౦</div></ol> +<ol start="1065"><div><bdi>౧౦౬౫. </bdi>౧౦౬౫</div></ol> +<ol start="1800"><div><bdi>౧౮౦౦. </bdi>౧౮౦౦</div></ol> +<ol start="1860"><div><bdi>౧౮౬౦. </bdi>౧౮౬౦</div></ol> +<ol start="1865"><div><bdi>౧౮౬౫. </bdi>౧౮౬౫</div></ol> +<ol start="5865"><div><bdi>౫౮౬౫. </bdi>౫౮౬౫</div></ol> +<ol start="7005"><div><bdi>౭౦౦౫. </bdi>౭౦౦౫</div></ol> +<ol start="7800"><div><bdi>౭౮౦౦. </bdi>౭౮౦౦</div></ol> +<ol start="7864"><div><bdi>౭౮౬౪. </bdi>౭౮౬౪</div></ol> +<ol start="9999"><div><bdi>౯౯౯౯. </bdi>౯౯౯౯</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-150.html b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-150.html index 50dd1f3ef5c..3f4e3c71e0c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-150.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-150.html @@ -5,13 +5,13 @@ <title>telugu, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-150-ref.html'> <meta name="assert" content="list-style-type: telugu produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: telugu; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-151-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-151-ref.html new file mode 100644 index 00000000000..4fa96ff6627 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-151-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>telugu, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: telugu produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: telugu; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>౧. </bdi>౧.</div> +<div><bdi>౨. </bdi>౨.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-151.html b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-151.html index 141451c34c9..c763a65c76f 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-151.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/telugu/css3-counter-styles-151.html @@ -5,13 +5,13 @@ <title>telugu, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-151-ref.html'> <meta name="assert" content="list-style-type: telugu produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: telugu; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-152-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-152-ref.html new file mode 100644 index 00000000000..86ce1871610 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-152-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>thai, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:thai produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: thai; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>๑. </bdi>๑</div> +<div><bdi>๒. </bdi>๒</div> +<div><bdi>๓. </bdi>๓</div> +<div><bdi>๔. </bdi>๔</div> +<div><bdi>๕. </bdi>๕</div> +<div><bdi>๖. </bdi>๖</div> +<div><bdi>๗. </bdi>๗</div> +<div><bdi>๘. </bdi>๘</div> +<div><bdi>๙. </bdi>๙</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-152.html b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-152.html index 74b8ece9323..9b1e76a4c92 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-152.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-152.html @@ -5,13 +5,13 @@ <title>thai, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-152-ref.html'> <meta name="assert" content="list-style-type:thai produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: thai; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-153-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-153-ref.html new file mode 100644 index 00000000000..ad79008967f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-153-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>thai, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: thai produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: thai; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>๑๐. </bdi>๑๐</div></ol> +<ol start="11"><div><bdi>๑๑. </bdi>๑๑</div></ol> +<ol start="12"><div><bdi>๑๒. </bdi>๑๒</div></ol> +<ol start="43"><div><bdi>๔๓. </bdi>๔๓</div></ol> +<ol start="77"><div><bdi>๗๗. </bdi>๗๗</div></ol> +<ol start="80"><div><bdi>๘๐. </bdi>๘๐</div></ol> +<ol start="99"><div><bdi>๙๙. </bdi>๙๙</div></ol> +<ol start="100"><div><bdi>๑๐๐. </bdi>๑๐๐</div></ol> +<ol start="101"><div><bdi>๑๐๑. </bdi>๑๐๑</div></ol> +<ol start="222"><div><bdi>๒๒๒. </bdi>๒๒๒</div></ol> +<ol start="540"><div><bdi>๕๔๐. </bdi>๕๔๐</div></ol> +<ol start="999"><div><bdi>๙๙๙. </bdi>๙๙๙</div></ol> +<ol start="1000"><div><bdi>๑๐๐๐. </bdi>๑๐๐๐</div></ol> +<ol start="1005"><div><bdi>๑๐๐๕. </bdi>๑๐๐๕</div></ol> +<ol start="1060"><div><bdi>๑๐๖๐. </bdi>๑๐๖๐</div></ol> +<ol start="1065"><div><bdi>๑๐๖๕. </bdi>๑๐๖๕</div></ol> +<ol start="1800"><div><bdi>๑๘๐๐. </bdi>๑๘๐๐</div></ol> +<ol start="1860"><div><bdi>๑๘๖๐. </bdi>๑๘๖๐</div></ol> +<ol start="1865"><div><bdi>๑๘๖๕. </bdi>๑๘๖๕</div></ol> +<ol start="5865"><div><bdi>๕๘๖๕. </bdi>๕๘๖๕</div></ol> +<ol start="7005"><div><bdi>๗๐๐๕. </bdi>๗๐๐๕</div></ol> +<ol start="7800"><div><bdi>๗๘๐๐. </bdi>๗๘๐๐</div></ol> +<ol start="7864"><div><bdi>๗๘๖๔. </bdi>๗๘๖๔</div></ol> +<ol start="9999"><div><bdi>๙๙๙๙. </bdi>๙๙๙๙</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-153.html b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-153.html index a5918a6135b..9de0fae60fc 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-153.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-153.html @@ -5,13 +5,13 @@ <title>thai, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-153-ref.html'> <meta name="assert" content="list-style-type: thai produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: thai; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-154-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-154-ref.html new file mode 100644 index 00000000000..1fec12e81ab --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-154-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>thai, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: thai produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: thai; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>๑. </bdi>๑.</div> +<div><bdi>๒. </bdi>๒.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-154.html b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-154.html index 876c0ef9259..5362b78e412 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-154.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/thai/css3-counter-styles-154.html @@ -5,13 +5,13 @@ <title>thai, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-154-ref.html'> <meta name="assert" content="list-style-type: thai produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: thai; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-155-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-155-ref.html new file mode 100644 index 00000000000..e9906b1adfd --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-155-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>tibetan, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type:tibetan produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: tibetan; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>༡. </bdi>༡</div> +<div><bdi>༢. </bdi>༢</div> +<div><bdi>༣. </bdi>༣</div> +<div><bdi>༤. </bdi>༤</div> +<div><bdi>༥. </bdi>༥</div> +<div><bdi>༦. </bdi>༦</div> +<div><bdi>༧. </bdi>༧</div> +<div><bdi>༨. </bdi>༨</div> +<div><bdi>༩. </bdi>༩</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-155.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-155.html index 0467f6f6097..2e7b9ea3749 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-155.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-155.html @@ -5,13 +5,13 @@ <title>tibetan, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-155-ref.html'> <meta name="assert" content="list-style-type:tibetan produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: tibetan; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-156-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-156-ref.html new file mode 100644 index 00000000000..7d714763dc7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-156-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>tibetan, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: tibetan produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: tibetan; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start="10"><div><bdi>༡༠. </bdi>༡༠</div></ol> +<ol start="11"><div><bdi>༡༡. </bdi>༡༡</div></ol> +<ol start="12"><div><bdi>༡༢. </bdi>༡༢</div></ol> +<ol start="43"><div><bdi>༤༣. </bdi>༤༣</div></ol> +<ol start="77"><div><bdi>༧༧. </bdi>༧༧</div></ol> +<ol start="80"><div><bdi>༨༠. </bdi>༨༠</div></ol> +<ol start="99"><div><bdi>༩༩. </bdi>༩༩</div></ol> +<ol start="100"><div><bdi>༡༠༠. </bdi>༡༠༠</div></ol> +<ol start="101"><div><bdi>༡༠༡. </bdi>༡༠༡</div></ol> +<ol start="222"><div><bdi>༢༢༢. </bdi>༢༢༢</div></ol> +<ol start="540"><div><bdi>༥༤༠. </bdi>༥༤༠</div></ol> +<ol start="999"><div><bdi>༩༩༩. </bdi>༩༩༩</div></ol> +<ol start="1000"><div><bdi>༡༠༠༠. </bdi>༡༠༠༠</div></ol> +<ol start="1005"><div><bdi>༡༠༠༥. </bdi>༡༠༠༥</div></ol> +<ol start="1060"><div><bdi>༡༠༦༠. </bdi>༡༠༦༠</div></ol> +<ol start="1065"><div><bdi>༡༠༦༥. </bdi>༡༠༦༥</div></ol> +<ol start="1800"><div><bdi>༡༨༠༠. </bdi>༡༨༠༠</div></ol> +<ol start="1860"><div><bdi>༡༨༦༠. </bdi>༡༨༦༠</div></ol> +<ol start="1865"><div><bdi>༡༨༦༥. </bdi>༡༨༦༥</div></ol> +<ol start="5865"><div><bdi>༥༨༦༥. </bdi>༥༨༦༥</div></ol> +<ol start="7005"><div><bdi>༧༠༠༥. </bdi>༧༠༠༥</div></ol> +<ol start="7800"><div><bdi>༧༨༠༠. </bdi>༧༨༠༠</div></ol> +<ol start="7864"><div><bdi>༧༨༦༤. </bdi>༧༨༦༤</div></ol> +<ol start="9999"><div><bdi>༩༩༩༩. </bdi>༩༩༩༩</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-156.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-156.html index 4dba20d6924..5827e0ad774 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-156.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-156.html @@ -5,13 +5,13 @@ <title>tibetan, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-156-ref.html'> <meta name="assert" content="list-style-type: tibetan produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: tibetan; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-157-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-157-ref.html new file mode 100644 index 00000000000..00dd3ea0719 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-157-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>tibetan, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: tibetan produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: tibetan; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>༡. </bdi>༡.</div> +<div><bdi>༢. </bdi>༢.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-157.html b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-157.html index 5aeb79c1135..47aaa4660b7 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-157.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/tibetan/css3-counter-styles-157.html @@ -5,13 +5,13 @@ <title>tibetan, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-157-ref.html'> <meta name="assert" content="list-style-type: tibetan produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: tibetan; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-086-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-086-ref.html new file mode 100644 index 00000000000..4ca4ed52992 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-086-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-formal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to trad-chinese-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>零、</bdi>零、</div></ol> +<ol start='1'><div><bdi>壹、</bdi>壹、</div></ol> +<ol start='2'><div><bdi>貳、</bdi>貳、</div></ol> +<ol start='3'><div><bdi>參、</bdi>參、</div></ol> +<ol start='4'><div><bdi>肆、</bdi>肆、</div></ol> +<ol start='5'><div><bdi>伍、</bdi>伍、</div></ol> +<ol start='6'><div><bdi>陸、</bdi>陸、</div></ol> +<ol start='7'><div><bdi>柒、</bdi>柒、</div></ol> +<ol start='8'><div><bdi>捌、</bdi>捌、</div></ol> +<ol start='9'><div><bdi>玖、</bdi>玖、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-086.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-086.html index b47b091a2fb..3acb5ad2d70 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-086.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-086.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-086-ref.html'> <meta name="assert" content="Setting list-style-type to trad-chinese-formal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-formal; } +ol { list-style-type: trad-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-087-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-087-ref.html new file mode 100644 index 00000000000..39de8cd3568 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-087-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-formal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to trad-chinese-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>壹拾、</bdi>壹拾、</div></ol> +<ol start='11'><div><bdi>壹拾壹、</bdi>壹拾壹、</div></ol> +<ol start='12'><div><bdi>壹拾貳、</bdi>壹拾貳、</div></ol> +<ol start='43'><div><bdi>肆拾參、</bdi>肆拾參、</div></ol> +<ol start='77'><div><bdi>柒拾柒、</bdi>柒拾柒、</div></ol> +<ol start='80'><div><bdi>捌拾、</bdi>捌拾、</div></ol> +<ol start='99'><div><bdi>玖拾玖、</bdi>玖拾玖、</div></ol> +<ol start='100'><div><bdi>壹佰、</bdi>壹佰、</div></ol> +<ol start='101'><div><bdi>壹佰零壹、</bdi>壹佰零壹、</div></ol> +<ol start='222'><div><bdi>貳佰貳拾貳、</bdi>貳佰貳拾貳、</div></ol> +<ol start='540'><div><bdi>伍佰肆拾、</bdi>伍佰肆拾、</div></ol> +<ol start='999'><div><bdi>玖佰玖拾玖、</bdi>玖佰玖拾玖、</div></ol> +<ol start='1000'><div><bdi>壹仟、</bdi>壹仟、</div></ol> +<ol start='1005'><div><bdi>壹仟零伍、</bdi>壹仟零伍、</div></ol> +<ol start='1060'><div><bdi>壹仟零陸拾、</bdi>壹仟零陸拾、</div></ol> +<ol start='1065'><div><bdi>壹仟零陸拾伍、</bdi>壹仟零陸拾伍、</div></ol> +<ol start='1800'><div><bdi>壹仟捌佰、</bdi>壹仟捌佰、</div></ol> +<ol start='1860'><div><bdi>壹仟捌佰陸拾、</bdi>壹仟捌佰陸拾、</div></ol> +<ol start='1865'><div><bdi>壹仟捌佰陸拾伍、</bdi>壹仟捌佰陸拾伍、</div></ol> +<ol start='5865'><div><bdi>伍仟捌佰陸拾伍、</bdi>伍仟捌佰陸拾伍、</div></ol> +<ol start='7005'><div><bdi>柒仟零伍、</bdi>柒仟零伍、</div></ol> +<ol start='7800'><div><bdi>柒仟捌佰、</bdi>柒仟捌佰、</div></ol> +<ol start='7865'><div><bdi>柒仟捌佰陸拾伍、</bdi>柒仟捌佰陸拾伍、</div></ol> +<ol start='9999'><div><bdi>玖仟玖佰玖拾玖、</bdi>玖仟玖佰玖拾玖、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-087.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-087.html index 887d024f011..cd70ee34d56 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-087.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-087.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-087-ref.html'> <meta name="assert" content="Setting list-style-type to trad-chinese-formal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-formal; } +ol { list-style-type: trad-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-088-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-088-ref.html new file mode 100644 index 00000000000..85408b9994d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-088-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-formal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: trad-chinese-formal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits and is NOT the same as the right side. Score as Partial if the columns of the 2nd and 3rd lines are the same (ie. fallback was used). In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>玖仟玖佰玖拾玖、</bdi>玖仟玖佰玖拾玖</div> +<div><bdi>一〇〇〇〇、</bdi>一〇〇〇〇</div> +<div><bdi>一〇〇〇一、</bdi>一〇〇〇一</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-088.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-088.html index d97f156885b..a7a6abae643 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-088.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-088.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-088-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: trad-chinese-formal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-formal; } +ol { list-style-type: trad-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-089-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-089-ref.html new file mode 100644 index 00000000000..47ecab2f9d7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-089-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-formal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to trad-chinese-formal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>負壹拾壹、</bdi>負壹拾壹、</div><div><bdi>負壹拾、</bdi>負壹拾、</div><div><bdi>負玖、</bdi>負玖、</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-089.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-089.html index 69c24c88137..ec96c7dfc56 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-089.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-089.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-089-ref.html'> <meta name="assert" content="With list-style-type set to trad-chinese-formal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-formal; } +ol { list-style-type: trad-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-090-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-090-ref.html new file mode 100644 index 00000000000..433ea576e31 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-090-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-formal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to trad-chinese-formal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-formal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>壹、</bdi>壹、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-090.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-090.html index e5fbb361dbe..2153679bcb3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-090.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-formal/css3-counter-styles-090.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-090-ref.html'> <meta name="assert" content="Setting list-style-type to trad-chinese-formal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-formal; } +ol { list-style-type: trad-chinese-formal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-081-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-081-ref.html new file mode 100644 index 00000000000..bf96d94e38d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-081-ref.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-informal, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to trad-chinese-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='0'><div><bdi>零、</bdi>零、</div></ol> +<ol start='1'><div><bdi>一、</bdi>一、</div></ol> +<ol start='2'><div><bdi>二、</bdi>二、</div></ol> +<ol start='3'><div><bdi>三、</bdi>三、</div></ol> +<ol start='4'><div><bdi>四、</bdi>四、</div></ol> +<ol start='5'><div><bdi>五、</bdi>五、</div></ol> +<ol start='6'><div><bdi>六、</bdi>六、</div></ol> +<ol start='7'><div><bdi>七、</bdi>七、</div></ol> +<ol start='8'><div><bdi>八、</bdi>八、</div></ol> +<ol start='9'><div><bdi>九、</bdi>九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-081.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-081.html index 89ec3f12c63..64ce4a6eed3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-081.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-081.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-081-ref.html'> <meta name="assert" content="Setting list-style-type to trad-chinese-informal will produce list of up to 9 items numbering as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-informal; } +ol { list-style-type: trad-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-082-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-082-ref.html new file mode 100644 index 00000000000..e4de29c2e5f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-082-ref.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-informal, 10-9999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to trad-chinese-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='10'><div><bdi>十、</bdi>十、</div></ol> +<ol start='11'><div><bdi>十一、</bdi>十一、</div></ol> +<ol start='12'><div><bdi>十二、</bdi>十二、</div></ol> +<ol start='43'><div><bdi>四十三、</bdi>四十三、</div></ol> +<ol start='77'><div><bdi>七十七、</bdi>七十七、</div></ol> +<ol start='80'><div><bdi>八十、</bdi>八十、</div></ol> +<ol start='99'><div><bdi>九十九、</bdi>九十九、</div></ol> +<ol start='100'><div><bdi>一百、</bdi>一百、</div></ol> +<ol start='101'><div><bdi>一百零一、</bdi>一百零一、</div></ol> +<ol start='222'><div><bdi>二百二十二、</bdi>二百二十二、</div></ol> +<ol start='540'><div><bdi>五百四十、</bdi>五百四十、</div></ol> +<ol start='999'><div><bdi>九百九十九、</bdi>九百九十九、</div></ol> +<ol start='1000'><div><bdi>一千、</bdi>一千、</div></ol> +<ol start='1005'><div><bdi>一千零五、</bdi>一千零五、</div></ol> +<ol start='1060'><div><bdi>一千零六十、</bdi>一千零六十、</div></ol> +<ol start='1065'><div><bdi>一千零六十五、</bdi>一千零六十五、</div></ol> +<ol start='1800'><div><bdi>一千八百、</bdi>一千八百、</div></ol> +<ol start='1860'><div><bdi>一千八百六十、</bdi>一千八百六十、</div></ol> +<ol start='1865'><div><bdi>一千八百六十五、</bdi>一千八百六十五、</div></ol> +<ol start='5865'><div><bdi>五千八百六十五、</bdi>五千八百六十五、</div></ol> +<ol start='7005'><div><bdi>七千零五、</bdi>七千零五、</div></ol> +<ol start='7800'><div><bdi>七千八百、</bdi>七千八百、</div></ol> +<ol start='7865'><div><bdi>七千八百六十五、</bdi>七千八百六十五、</div></ol> +<ol start='9999'><div><bdi>九千九百九十九、</bdi>九千九百九十九、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-082.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-082.html index bac46b3c85d..3b552ee3486 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-082.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-082.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-082-ref.html'> <meta name="assert" content="Setting list-style-type to trad-chinese-informal will produce list numbering after 9 as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-informal; } +ol { list-style-type: trad-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-083-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-083-ref.html new file mode 100644 index 00000000000..39f77afc677 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-083-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-informal, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="[Exploratory] list-style-type: trad-chinese-informal produces counter values outside its range without using the prescribed fallback style."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test fails if the two columns of the first line are NOT the same. Otherwise, test passes only if the left column of the 2nd and 3rd lines is NOT decimal digits and is NOT the same as the right side. Score as Partial if the columns of the 2nd and 3rd lines are the same (ie. fallback was used). In all this IGNORE the suffix.</p> +<div class="test"><ol start="9999"> +<div><bdi>九千九百九十九、</bdi>九千九百九十九</div> +<div><bdi>一〇〇〇〇、</bdi>一〇〇〇〇</div> +<div><bdi>一〇〇〇一、</bdi>一〇〇〇一</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-083.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-083.html index d8935b0a898..5a341f68ea2 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-083.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-083.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-083-ref.html'> <meta name="assert" content="[Exploratory] list-style-type: trad-chinese-informal produces counter values outside its range without using the prescribed fallback style."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-informal; } +ol { list-style-type: trad-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-084-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-084-ref.html new file mode 100644 index 00000000000..be4e74c266a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-084-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-informal, negative</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="With list-style-type set to trad-chinese-informal, negative list markers will be rendered according to the rules described."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start="-11"><div><bdi>負十一、</bdi>負十一、</div><div><bdi>負十、</bdi>負十、</div><div><bdi>負九、</bdi>負九、</div></ol></div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-084.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-084.html index 2387abd64a4..bf386b103f5 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-084.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-084.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-084-ref.html'> <meta name="assert" content="With list-style-type set to trad-chinese-informal, negative list markers will be rendered according to the rules described."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-informal; } +ol { list-style-type: trad-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-085-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-085-ref.html new file mode 100644 index 00000000000..bb6cebbe094 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-085-ref.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>trad-chinese-informal, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to trad-chinese-informal will produce a suffix as described in the CSS3 Counter Styles module."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: trad-chinese-informal; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> + + +<div class="test"><ol start='1'><div><bdi>一、</bdi>一、</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-085.html b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-085.html index 1dba111c3b9..94b575ffafc 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-085.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/trad-chinese-informal/css3-counter-styles-085.html @@ -6,12 +6,12 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#complex-cjk'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-085-ref.html'> <meta name="assert" content="Setting list-style-type to trad-chinese-informal will produce a suffix as described in the CSS3 Counter Styles module."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: trad-chinese-informal; } +ol { list-style-type: trad-chinese-informal; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-107-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-107-ref.html new file mode 100644 index 00000000000..1cb0edc5ce7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-107-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-armenian, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: upper-armenian produces numbers up to 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: upper-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol> +<div><bdi>Ա. </bdi>Ա</div> +<div><bdi>Բ. </bdi>Բ</div> +<div><bdi>Գ. </bdi>Գ</div> +<div><bdi>Դ. </bdi>Դ</div> +<div><bdi>Ե. </bdi>Ե</div> +<div><bdi>Զ. </bdi>Զ</div> +<div><bdi>Է. </bdi>Է</div> +<div><bdi>Ը. </bdi>Ը</div> +<div><bdi>Թ. </bdi>Թ</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-107.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-107.html index 52a86fc22e5..f997ebd07b6 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-107.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-107.html @@ -5,13 +5,13 @@ <title>upper-armenian, 0-9</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-107-ref.html'> <meta name="assert" content="list-style-type: upper-armenian produces numbers up to 9 per the spec."> <style type='text/css'> ol li { list-style-type: upper-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-108-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-108-ref.html new file mode 100644 index 00000000000..ce9f65587a7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-108-ref.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-armenian, 10+</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: upper-armenian produces numbers after 9 per the spec."> +<style type='text/css'> +ol li { list-style-type: upper-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>Ժ. </bdi>Ժ</div></ol> +<ol start='11'><div><bdi>ԺԱ. </bdi>ԺԱ</div></ol> +<ol start='12'><div><bdi>ԺԲ. </bdi>ԺԲ</div></ol> +<ol start='43'><div><bdi>ԽԳ. </bdi>ԽԳ</div></ol> +<ol start='77'><div><bdi>ՀԷ. </bdi>ՀԷ</div></ol> +<ol start='80'><div><bdi>Ձ. </bdi>Ձ</div></ol> +<ol start='99'><div><bdi>ՂԹ. </bdi>ՂԹ</div></ol> +<ol start='100'><div><bdi>Ճ. </bdi>Ճ</div></ol> +<ol start='101'><div><bdi>ՃԱ. </bdi>ՃԱ</div></ol> +<ol start='222'><div><bdi>ՄԻԲ. </bdi>ՄԻԲ</div></ol> +<ol start='540'><div><bdi>ՇԽ. </bdi>ՇԽ</div></ol> +<ol start='999'><div><bdi>ՋՂԹ. </bdi>ՋՂԹ</div></ol> +<ol start='1000'><div><bdi>Ռ. </bdi>Ռ</div></ol> +<ol start='1005'><div><bdi>ՌԵ. </bdi>ՌԵ</div></ol> +<ol start='1060'><div><bdi>ՌԿ. </bdi>ՌԿ</div></ol> +<ol start='1065'><div><bdi>ՌԿԵ. </bdi>ՌԿԵ</div></ol> +<ol start='1800'><div><bdi>ՌՊ. </bdi>ՌՊ</div></ol> +<ol start='1860'><div><bdi>ՌՊԿ. </bdi>ՌՊԿ</div></ol> +<ol start='1865'><div><bdi>ՌՊԿԵ. </bdi>ՌՊԿԵ</div></ol> +<ol start='5865'><div><bdi>ՐՊԿԵ. </bdi>ՐՊԿԵ</div></ol> +<ol start='7005'><div><bdi>ՒԵ. </bdi>ՒԵ</div></ol> +<ol start='7800'><div><bdi>ՒՊ. </bdi>ՒՊ</div></ol> +<ol start='7865'><div><bdi>ՒՊԿԵ. </bdi>ՒՊԿԵ</div></ol> +<ol start='9999'><div><bdi>ՔՋՂԹ. </bdi>ՔՋՂԹ</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-108.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-108.html index 5781ddc0287..7a55dbbc695 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-108.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-108.html @@ -5,13 +5,13 @@ <title>upper-armenian, 10+</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-108-ref.html'> <meta name="assert" content="list-style-type: upper-armenian produces numbers after 9 per the spec."> <style type='text/css'> ol li { list-style-type: upper-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-109-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-109-ref.html new file mode 100644 index 00000000000..38b7b1f8acf --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-109-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-armenian, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: upper-armenian produces numbers in the fallback counter style above the limit per the spec."> +<style type='text/css'> +ol li { list-style-type: upper-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='9999'> +<div><bdi>ՔՋՂԹ. </bdi>ՔՋՂԹ</div> +<div><bdi>10000. </bdi>10000</div> +<div><bdi>10001. </bdi>10001</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +The test relies on the start attribute working. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-109.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-109.html index f355f98613b..0299db5484c 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-109.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-109.html @@ -5,13 +5,13 @@ <title>upper-armenian, outside range</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-109-ref.html'> <meta name="assert" content="list-style-type: upper-armenian produces numbers in the fallback counter style above the limit per the spec."> <style type='text/css'> ol li { list-style-type: upper-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-110-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-110-ref.html new file mode 100644 index 00000000000..396d5494612 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-110-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-armenian, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: upper-armenian produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: upper-armenian; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class='test'><ol> +<div><bdi>Ա. </bdi>Ա.</div> +<div><bdi>Բ. </bdi>Բ.</div> +</ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-110.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-110.html index 6a9baeb3be9..9687b6c31c5 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-110.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-armenian/css3-counter-styles-110.html @@ -5,13 +5,13 @@ <title>upper-armenian, suffix</title> <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-110-ref.html'> <meta name="assert" content="list-style-type: upper-armenian produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: upper-armenian; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-023-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-023-ref.html new file mode 100644 index 00000000000..fe6b2a1283b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-023-ref.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-roman, 0-9</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: upper-roman produces numbers up to 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: upper-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"><ol> +<div><bdi>I. </bdi>I</div> +<div><bdi>II. </bdi>II</div> +<div><bdi>III. </bdi>III</div> +<div><bdi>IV. </bdi>IV</div> +<div><bdi>V. </bdi>V</div> +<div><bdi>VI. </bdi>VI</div> +<div><bdi>VII. </bdi>VII</div> +<div><bdi>VIII. </bdi>VIII</div> +<div><bdi>IX. </bdi>IX</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-023.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-023.html index 6c9828aa0fc..cab6d4694fa 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-023.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-023.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-023-ref.html'> <meta name="assert" content="list-style: upper-roman produces numbers up to 9 items per the spec."> <style type='text/css'> ol li { list-style-type: upper-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024-ref.html new file mode 100644 index 00000000000..7ffb08f5238 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024-ref.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-roman, 10-3999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style: upper-roman produces numbers after 9 items per the spec."> +<style type='text/css'> +ol li { list-style-type: upper-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"> +<ol start='10'><div><bdi>X. </bdi>X</div></ol> +<ol start='11'><div><bdi>XI. </bdi>XI</div></ol> +<ol start='12'><div><bdi>XII. </bdi>XII</div></ol> +<ol start='43'><div><bdi>XLIII. </bdi>XLIII</div></ol> +<ol start='77'><div><bdi>LXXVII. </bdi>LXXVII</div></ol> +<ol start='80'><div><bdi>LXXX. </bdi>LXXX</div></ol> +<ol start='99'><div><bdi>XCIX. </bdi>XCIX</div></ol> +<ol start='100'><div><bdi>C. </bdi>C</div></ol> +<ol start='101'><div><bdi>CI. </bdi>CI</div></ol> +<ol start='222'><div><bdi>CCXXII. </bdi>CCXXII</div></ol> +<ol start='540'><div><bdi>DXL. </bdi>DXL</div></ol> +<ol start='999'><div><bdi>CMXCIX. </bdi>CMXCIX</div></ol> +<ol start='1000'><div><bdi>M. </bdi>M</div></ol> +<ol start='1005'><div><bdi>MV. </bdi>MV</div></ol> +<ol start='1060'><div><bdi>MLX. </bdi>MLX</div></ol> +<ol start='1065'><div><bdi>MLXV. </bdi>MLXV</div></ol> +<ol start='1800'><div><bdi>MDCCC. </bdi>MDCCC</div></ol> +<ol start='1860'><div><bdi>MDCCCLX. </bdi>MDCCCLX</div></ol> +<ol start='1865'><div><bdi>MDCCCLXV. </bdi>MDCCCLXV</div></ol> +<ol start='2555'><div><bdi>MMDLV. </bdi>MMDLV</div></ol> +<ol start='3000'><div><bdi>MMM. </bdi>MMM</div></ol> +<ol start='3999'><div><bdi>MMMCMXCIX. </bdi>MMMCMXCIX</div></ol> +</div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024.html index 65b16180c4d..e3a539ccd89 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-024-ref.html'> <meta name="assert" content="list-style: upper-roman produces numbers after 9 items per the spec."> <style type='text/css'> ol li { list-style-type: upper-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024a-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024a-ref.html new file mode 100644 index 00000000000..07d33186d0d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024a-ref.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-roman, 3000-3999</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='author' title='Chris Lilley' href='mailto:chris@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="Setting list-style-type to upper-roman will produce list of up to 9 items in the range range: 1 to 3999."> +<style type='text/css'> +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +ol li { list-style-type: upper-roman; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, ignoring the suffix.</p> + + +<div class="test"><ol start='3000'><div><bdi>MMM. </bdi>MMM.</div></ol> +<ol start='3555'><div><bdi>MMMDLV. </bdi>MMMDLV.</div></ol> +<ol start='3998'><div><bdi>MMMCMXCVIII. </bdi>MMMCMXCVIII.</div></ol> +<ol start='3999'><div><bdi>MMMCMXCIX. </bdi>MMMCMXCIX.</div></ol> +</div> + + +<!--Notes: +You will need an appropriate font to run this test. + +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024a.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024a.html index f51f74b541a..336503883e3 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024a.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-024a.html @@ -7,12 +7,12 @@ <link rel='author' title='Chris Lilley' href='mailto:chris@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-024a-ref.html'> <meta name="assert" content="Setting list-style-type to upper-roman will produce list of up to 9 items in the range range: 1 to 3999."> <style type='text/css'> .test { font-size: 25px; } ol { margin: 0; padding-left: 8em; } -ol li { list-style-type: upper-roman; } +ol li { list-style-type: upper-roman; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-025-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-025-ref.html new file mode 100644 index 00000000000..cf07ac89759 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-025-ref.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-roman, outside range</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: upper-roman produces numbers in the fallback counter style above the limit per the spec"> +<style type='text/css'> +ol li { list-style-type: upper-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the two columns are the same, IGNORING the suffix.</p> +<div class="test"><ol start='3999'> +<div><bdi>MMMCMXCIX. </bdi>MMMCMXCIX</div> +<div><bdi>4000. </bdi>4000</div></ol> +<ol start='4001'><div><bdi>4001. </bdi>4001</div> +<div><bdi>4002. </bdi>4002</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-025.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-025.html index 9417d9e44f7..322679183f2 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-025.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-025.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-025-ref.html'> <meta name="assert" content="list-style-type: upper-roman produces numbers in the fallback counter style above the limit per the spec"> <style type='text/css'> ol li { list-style-type: upper-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-026-ref.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-026-ref.html new file mode 100644 index 00000000000..27cb359402f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-026-ref.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html lang="en" > +<head> +<meta charset="utf-8"/> +<title>upper-roman, suffix</title> +<link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> +<link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> +<link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> +<meta name='flags' content='font'> +<meta name="assert" content="list-style-type: upper-roman produces a suffix per the spec."> +<style type='text/css'> +ol li { list-style-type: upper-roman; } +/* the following CSS is not part of the test */ +.test { font-size: 25px; } +ol { margin: 0; padding-left: 8em; } +</style> +</head> +<body> +<p class="instructions">Test passes if the numbers AND the suffix in each of the two columns is the same.</p> +<div class="test"><ol> +<div><bdi>I. </bdi>I.</div> +<div><bdi>II. </bdi>II.</div> +</ol></div> +<!--Notes: +You will need an appropriate font to run this test. +To see the ASCII decimal number associated with a row, mouse over it and the number will pop up in a tooltip. +--> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-026.html b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-026.html index e106e053772..40f78b2ae39 100644 --- a/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-026.html +++ b/tests/wpt/web-platform-tests/css/css-counter-styles/upper-roman/css3-counter-styles-026.html @@ -6,13 +6,13 @@ <link rel='author' title='Richard Ishida' href='mailto:ishida@w3.org'> <link rel='help' href='http://www.w3.org/TR/css-counter-styles-3/#simple-numeric'> <link rel="reviewer" title="Chris Lilley" href="mailto:chris@w3.org" /> -<meta name='flags' content='font'> +<link rel='match' href='css3-counter-styles-026-ref.html'> <meta name="assert" content="list-style-type: upper-roman produces a suffix per the spec."> <style type='text/css'> ol li { list-style-type: upper-roman; } /* the following CSS is not part of the test */ .test { font-size: 25px; } -ol { margin: 0; padding-left: 8em; } +ol { margin: 0; padding-left: 8em; list-style-position: inside; } </style> </head> <body> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/flexbox-min-width-auto-005.html b/tests/wpt/web-platform-tests/css/css-flexbox/flexbox-min-width-auto-005.html new file mode 100644 index 00000000000..018d8e65982 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/flexbox-min-width-auto-005.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>CSS Flexible Box Test: Aspect ratio handling of images</title> +<link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com" /> +<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#min-size-auto" /> +<link rel="match" href="reference/flexbox-min-width-auto-005-ref.html" /> +<meta name="assert" content="Test that min-width:auto does not incorrectly stretch items with aspect ratio" /> + +<style> +.reference-overlapped-red { + position: absolute; + background-color: red; + width: 100px; + height: 50px; + z-index: -1; +} +.constrained-flex { + display: flex; + height: 50px; +} +br { margin: 50px; } +</style> + +<p>Test passes if there is <strong>no red</strong> visible on the page.</p> + +<div class="reference-overlapped-red"></div> +<div class="constrained-flex"> + <img src="support/40x20-green.png" /> +</div> + +<br> + +<div class="reference-overlapped-red" style="width: 40px;"></div> +<div style="display: flex"> + <div class="constrained-flex"> + <img src="support/40x20-green.png" /> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/flexbox-min-width-auto-006.html b/tests/wpt/web-platform-tests/css/css-flexbox/flexbox-min-width-auto-006.html new file mode 100644 index 00000000000..7ff72d6e3d5 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/flexbox-min-width-auto-006.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>CSS Flexible Box Test: Aspect ratio handling of images</title> +<link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com" /> +<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#min-size-auto" /> +<link rel="match" href="reference/flexbox-min-width-auto-006-ref.html" /> +<meta name="assert" content="Test that min-width:auto does not incorrectly stretch items with aspect ratio" /> + +<style> +#reference-overlapped-red { + position: relative; + background-color: red; + width: 10px; + height: 10px; + top: 40px; + z-index: -1; +} +.constrained-width-flex { + width: 100px; + display: flex; + border: 1px solid black; +} +.constrained-height-flex { + display: flex; + height: 100px; +} +</style> + +<p>Test passes if there are a (vertically centered) 20x20 and a 60x100 green boxes enclosed on each 100x100 square.</p> + +<div class="constrained-width-flex"> + <div class="constrained-height-flex"> + <img src="data:image/svg+xml, + <svg width='20' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'> + <rect width='100%' height='100%' fill='green'/> + </svg>"/> + </div> +</div> + +<br> + +<div class="constrained-width-flex"> + <div class="constrained-height-flex"> + <img src="support/60x60-green.png"/> + </div> +</div>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/frameset-crash.html b/tests/wpt/web-platform-tests/css/css-flexbox/frameset-crash.html new file mode 100644 index 00000000000..4052717d54e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/frameset-crash.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<link rel="author" title="David Grogan" href="dgrogan@chromium.org"> +<link rel="help" href="https://crbug.com/1173843"> +<meta name="assert" content="No crash when a flexbox lays out a frameset with a border in the main axis direction."> +<style> +frameset { + border-right: 50px solid red; +} +</style> + +<div id='flex' style="display: flex"></div> + +<script> + // I couldn't get the parser to accept a framset inside a div, so this uses + // JS instead. + flex.appendChild(document.createElement('frameset')); +</script> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/percentage-descendant-of-anonymous-flex-item.html b/tests/wpt/web-platform-tests/css/css-flexbox/percentage-descendant-of-anonymous-flex-item.html new file mode 100644 index 00000000000..592b5212ea6 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/percentage-descendant-of-anonymous-flex-item.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Flexbox Test: percentage heights in descendants of anonymous flex items</title> +<link href="resources/flexbox.css" rel="stylesheet"> +<link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com"> +<link rel="match" href="reference/percentage-descendant-of-anonymous-flex-item-ref.html"> +<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-items"> +<style> +.flexbox { + width: 200px; + height: 200px; +} +</style> +<!-- + Both Blink and WebKit implement buttons as flexboxes, which in this case, wrap the child in an anonymous box. + Anonymous boxes are skipped when computing percentage heights but we need to ensure that their children with + percentage heights are properly sized. +--> +<p>The test PASS if you see a 200x100 green rectangle inside a button.</p> +<div class="flexbox column"> + <div style="height: 50%;"> + <button style="width: 200px; height: 100%;"> + <div style="width: 200px; height: 100%; background-color: green;"></div> + </button> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/radiobutton-min-size.html b/tests/wpt/web-platform-tests/css/css-flexbox/radiobutton-min-size.html index 5703861bf73..1dd84b0766c 100644 --- a/tests/wpt/web-platform-tests/css/css-flexbox/radiobutton-min-size.html +++ b/tests/wpt/web-platform-tests/css/css-flexbox/radiobutton-min-size.html @@ -36,7 +36,5 @@ var check = document.getElementById("check"); test(function() { assert_equals(ref.offsetWidth, check.offsetWidth, "width should be equal"); - assert_equals(ref.offsetHeight, check.offsetHeight, - "height should be equal"); -}, "two radio button sizes are identical"); +}, "two radio button widths are identical"); </script> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/reference/flexbox-min-width-auto-005-ref.html b/tests/wpt/web-platform-tests/css/css-flexbox/reference/flexbox-min-width-auto-005-ref.html new file mode 100644 index 00000000000..d1e9d963d74 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/reference/flexbox-min-width-auto-005-ref.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>CSS Reference</title> +<link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com" /> +<style> +div { + background-color: green; + height: 50px; + width: 100px; +} +span { + display: inline-block; + background-color: green; + height: 50px; + width: 40px; +} +br { margin: 50px; } +</style> + +<p>Test passes if there is <strong>no red</strong> visible on the page.</p> +<div></div> + +<br> + +<span></span>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/reference/flexbox-min-width-auto-006-ref.html b/tests/wpt/web-platform-tests/css/css-flexbox/reference/flexbox-min-width-auto-006-ref.html new file mode 100644 index 00000000000..1d6cb7ae658 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/reference/flexbox-min-width-auto-006-ref.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>CSS Reference</title> +<link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com" /> +<style> +.box { + width: 100px; + height: 100px; + border: 1px solid black; +} +#green-square { + background: green; + margin-top: 40px; + width: 20px; + height: 20px; +} +#green-rectange { + background: green; + width: 60px; + height: 100px; +} +</style> + +<p>Test passes if there are a (vertically centered) 20x20 and a 60x100 green boxes enclosed on each 100x100 square.</p> +<div class="box"> + <div id="green-square"></div> +</div> + +<br> + +<div class="box"> + <div id="green-rectange"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/reference/percentage-descendant-of-anonymous-flex-item-ref.html b/tests/wpt/web-platform-tests/css/css-flexbox/reference/percentage-descendant-of-anonymous-flex-item-ref.html new file mode 100644 index 00000000000..78dac9724f7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/reference/percentage-descendant-of-anonymous-flex-item-ref.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Flexbox Test: percentage heights in descendants of anonymous flex items</title> +<link rel="author" title="Sergio Villar Senin" href="mailto:svillar@igalia.com"> +<p>The test PASS if you see a 200x100 green rectangle inside a button.</p> +<button style="width: 200px; height: 100px;"> + <div style="width: 200px; height: 100%; background-color: green;"></div> +</button> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/support/40x20-green.png b/tests/wpt/web-platform-tests/css/css-flexbox/support/40x20-green.png Binary files differnew file mode 100644 index 00000000000..c372de1dbd2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/support/40x20-green.png diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-narrow-content-2.html b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-narrow-content-2.html new file mode 100644 index 00000000000..96af10770f2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-narrow-content-2.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>CSS Flexbox Test: Flex item as table with narrow content</title> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com"> +<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#layout-algorithm" title="9. Flex Layout Algorithm"> +<meta name="assert" content="A flex item as a table uses the sizing algorithm of the flexbox"> +<link rel="match" href="../reference/ref-filled-green-100px-square-only.html"> +<p>Test passes if there is a filled green square.</p> +<div style="display:flex; flex-direction:column; width: 100px; height:200px; "> + <div style="display:table; flex:1 0; background:green;"> + <div style="width:100px; height:10px;"></div> + </div> + <div style="flex:1 0;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-specified-height.html b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-specified-height.html new file mode 100644 index 00000000000..f8f3740fcd4 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-specified-height.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>table is flex item</title> +<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org"> +<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com"> +<link rel="help" href="https://www.w3.org/TR/css-flexbox-1/#layout-algorithm" title="9. Flex Layout Algorithm"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#computing-the-table-height"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1692116"> +<link rel="match" href="../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Table's specified height does not count as another min-height for the purposes of the flexbox algorithm."> + +<p>Test passes if there is a filled green square.</p> + +<div style="display: flex; flex-direction: column; width: 100px;"> + <div style="display: table; width: 100px; height: 500px; background: green; flex: 0 0 100px;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-specified-width.html b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-specified-width.html index a4b370eb254..2d6850fdd73 100644 --- a/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-specified-width.html +++ b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-specified-width.html @@ -8,7 +8,8 @@ <p>Test passes if there is a filled green square.</p> -<!-- This test passed until Chrome 84 but has been broken since. Firefox also fails it. --> +<!-- This test passed until Chrome 84 but has been broken since. + Firefox fixed this test since version 87. --> <div style="display: flex;"> <div style="display: table; width: 500px; height: 100px; background: green; flex: 0 0 100px;"></div> diff --git a/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-stretch-cross-size-2.html b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-stretch-cross-size-2.html new file mode 100644 index 00000000000..04e5b1e342e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-flexbox/table-as-item-stretch-cross-size-2.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-flexbox-1/#valdef-align-items-stretch"> +<link rel="match" href="../reference/ref-filled-green-100px-square.xht"> +<meta name="assert" content="The table's cross-axis (inline-size) is stretched."> +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> +<div style="display: flex; flex-direction: column; width: 100px; height: 100px; background: red;"> + <table style="border-spacing: 0; flex-grow: 1;"> + <caption style="height: 10px; background: green;"></caption> + <caption style="height: 20px; background: green; caption-side: bottom;"></caption> + <td style="background: green;"></td> + </table> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-fonts/advance-override-ref.html b/tests/wpt/web-platform-tests/css/css-fonts/advance-override-ref.html new file mode 100644 index 00000000000..81109083b3c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-fonts/advance-override-ref.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-fonts-5/#descdef-font-face-advance-override"> +<link rel="assert" title="Tests that advance-override sets advance to characters rendered with the font face only"> +<title>Tests the advance-override descriptor of @font-face</title> +<style> +@font-face { + font-family: custom-font; + src: local(Ahem), url(/fonts/Ahem.ttf); + unicode-range: U+0-7F; /* ASCII only */ +} + +.target { + font: 20px custom-font, sans-serif; +} + +.letter-spacing { + letter-spacing: 1em; +} + +</style> + +<p>advance-override should affect Ahem characters only.</p> + +<div class="target"> + <span class="letter-spacing">XXX</span>一二三<span class="letter-spacing">XXX</span> +</div> + +<p>advance-override: 100% should be the same as no override.</p> + +<div class="target"> + XXX一二三XXX +</div> diff --git a/tests/wpt/web-platform-tests/css/css-fonts/advance-override.html b/tests/wpt/web-platform-tests/css/css-fonts/advance-override.html new file mode 100644 index 00000000000..2b4a6ca3823 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-fonts/advance-override.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel="author" href="mailto:xiaochengh@chromium.org"> +<link rel="help" href="https://drafts.csswg.org/css-fonts-5/#descdef-font-face-advance-override"> +<link rel="match" href="advance-override-ref.html"> +<link rel="assert" title="Tests that advance-override sets advance to characters rendered with the font face only"> +<title>Tests the advance-override descriptor of @font-face</title> +<style> +@font-face { + font-family: custom-font; + src: local(Ahem), url(/fonts/Ahem.ttf); + advance-override: 200%; + unicode-range: U+0-7F; /* ASCII only */ +} + +.target { + font: 20px custom-font, sans-serif; +} + +@font-face { + font-family: reference-font; + src: local(Ahem), url(/fonts/Ahem.ttf); + advance-override: 100%; + unicode-range: U+0-7F; /* ASCII only */ +} + +.reference { + font: 20px reference-font, sans-serif; +} + +</style> + +<p>advance-override should affect Ahem characters only.</p> + +<div class="target"> + XXX一二三XXX +</div> + +<p>advance-override: 100% should be the same as no override.</p> + +<div class="reference"> + XXX一二三XXX +</div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-001.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-001.html new file mode 100644 index 00000000000..bd744cf0cb3 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-001.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat"> +<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Checks that the min-height is reflected through the aspect-ratio for determining auto repeat tracks."> +<p>Test passes if there is a filled green square.</p> +<div style="display: inline-grid; background: green; aspect-ratio: 1/1; min-height: 60px; grid-template-columns: repeat(auto-fill, 50px);"></div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-002.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-002.html new file mode 100644 index 00000000000..3b74e792241 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-aspect-ratio-002.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat"> +<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Checks that the min-height is reflected through the aspect-ratio for determining auto repeat tracks."> +<p>Test passes if there is a filled green square.</p> +<div style="width: min-content; height: 100px; background: green;"> + <div style="height: 100%;"> + <div style="display: grid; aspect-ratio: 1/1; min-height: 60%; grid-template-columns: repeat(auto-fill, 50px);"></div> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-001.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-001.html new file mode 100644 index 00000000000..25378928872 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-001.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat"> +<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Checks that a dynamic change in containing block width changes the number of auto repeat columns."> +<p>Test passes if there is a filled green square.</p> +<div id="target" style="width: 0px; height: 100px;"> + <div style="display: inline-grid; background: green; height: 100px; min-width: 60%; grid-template-columns: repeat(auto-fill, 50px);"></div> +</div> +<script> +document.body.offsetTop; +document.getElementById('target').style.width = '100px'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-002.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-002.html new file mode 100644 index 00000000000..8df2158d1f1 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-002.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat"> +<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Checks that a dynamic change in containing block height changes the number of auto repeat rows."> +<p>Test passes if there is a filled green square.</p> +<div id="target" style="width: 100px; height: 0px;"> + <div style="display: inline-grid; background: green; width: 100px; min-height: 60%; grid-template-rows: repeat(auto-fill, 50px);"></div> +</div> +<script> +document.body.offsetTop; +document.getElementById('target').style.height = '100px'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html new file mode 100644 index 00000000000..c2fe3ff98a7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-dynamic-003.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-grid-1/#auto-repeat"> +<link rel="match" href="../../reference/ref-filled-green-100px-square-only.html"> +<meta name="assert" content="Checks that a dynamic change in containing block height changes the number of auto repeat columns."> +<p>Test passes if there is a filled green square.</p> +<div id="target" style="width: 100px; height: 0px;"> + <div style="display: inline-grid; background: green; min-height: 60%; grid-template-columns: repeat(auto-fill, 50px); grid-template-rows: 100px; aspect-ratio: 1/1;"></div> +</div> +<script> +document.body.offsetTop; +document.getElementById('target').style.height = '100px'; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-004-ref.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-004-ref.html new file mode 100644 index 00000000000..650b7d9eaf9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-004-ref.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Grid Layout Test: auto-repeat with multiple tracks and line names reference</title> + <link rel="author" title="Mozilla" href="https://mozilla.org"> + <style> + .columns { + border: 1px solid black; + width: 5px; + display: grid; + grid-template-columns: [u v] 10px [w] 10px [x] 10px [y v] 10px [w] 10px [x] 10px [y z]; + grid-auto-rows: 5px; + grid-column-gap: 3px; + /* Does not fit a whole-number of repetitions */ + width: 94px; + } + div > div { + background: blue; + } + </style> +</head> +<body> + <!-- u --> + <div class="columns"> + <div style="grid-column-start: u"></div> + </div> + <!-- Non-existant line name --> + <div class="columns"> + <div style="grid-column-start: nonesuch"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -1"></div> + </div> + <!-- Also non-existant, there is only one u --> + <div class="columns"> + <div style="grid-column-start: u 4"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u 9"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -6"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -7"></div> + </div> + <!-- v --> + <div class="columns"> + <div style="grid-column-start: v"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v -2"></div> + </div> + <!-- Also non-existant, there are only two v's --> + <div class="columns"> + <div style="grid-column-start: v 3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v -3"></div> + </div> + <!-- w --> + <div class="columns"> + <div style="grid-column-start: w"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w -2"></div> + </div> + <!-- Also non-existant, there are only two w's --> + <div class="columns"> + <div style="grid-column-start: w 3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w -3"></div> + </div> + <!-- x --> + <div class="columns"> + <div style="grid-column-start: x"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x -2"></div> + </div> + <!-- Also non-existant, there are only two x's --> + <div class="columns"> + <div style="grid-column-start: x 3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x -3"></div> + </div> + <!-- y --> + <div class="columns"> + <div style="grid-column-start: y"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y -2"></div> + </div> + <!-- Also non-existant, there are only two y's --> + <div class="columns"> + <div style="grid-column-start: y -3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y 3"></div> + </div> + <!-- z --> + <div class="columns"> + <div style="grid-column-start: z"></div> + </div> + <div class="columns"> + <div style="grid-column-start: z 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: z -1"></div> + </div> + <!-- Also non-existant, there is only one z --> + <div class="columns"> + <div style="grid-column-start: z 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: z -2"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-004.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-004.html new file mode 100644 index 00000000000..460de5f9e84 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-004.html @@ -0,0 +1,168 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Grid Layout Test: auto-repeat with multiple tracks and line names</title> + <link rel="author" title="Mozilla" href="https://mozilla.org"> + <link rel="help" href="https://www.w3.org/TR/css-grid-1/#repeat-notation"> + <link rel="match" href="grid-auto-repeat-multiple-values-004-ref.html"> + <style> + .columns { + border: 1px solid black; + display: grid; + grid-template-columns: [u] repeat(auto-fill, [v] 10px [w] 10px [x] 10px [y]) [z]; + grid-auto-rows: 5px; + grid-column-gap: 3px; + /* Does not fit a whole-number of repetitions */ + width: 94px; + } + div > div { + background: blue; + } + </style> +</head> +<body> + <!-- u --> + <div class="columns"> + <div style="grid-column-start: u"></div> + </div> + <!-- Non-existant line name --> + <div class="columns"> + <div style="grid-column-start: nonesuch"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -1"></div> + </div> + <!-- Also non-existant, there is only one u --> + <div class="columns"> + <div style="grid-column-start: u 4"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u 9"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -6"></div> + </div> + <div class="columns"> + <div style="grid-column-start: u -7"></div> + </div> + <!-- v --> + <div class="columns"> + <div style="grid-column-start: v"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v -2"></div> + </div> + <!-- Also non-existant, there are only two v's --> + <div class="columns"> + <div style="grid-column-start: v 3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: v -3"></div> + </div> + <!-- w --> + <div class="columns"> + <div style="grid-column-start: w"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w -2"></div> + </div> + <!-- Also non-existant, there are only two w's --> + <div class="columns"> + <div style="grid-column-start: w 3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: w -3"></div> + </div> + <!-- x --> + <div class="columns"> + <div style="grid-column-start: x"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x -2"></div> + </div> + <!-- Also non-existant, there are only two x's --> + <div class="columns"> + <div style="grid-column-start: x 3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: x -3"></div> + </div> + <!-- y --> + <div class="columns"> + <div style="grid-column-start: y"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y -1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y -2"></div> + </div> + <!-- Also non-existant, there are only two y's --> + <div class="columns"> + <div style="grid-column-start: y -3"></div> + </div> + <div class="columns"> + <div style="grid-column-start: y 3"></div> + </div> + <!-- z --> + <div class="columns"> + <div style="grid-column-start: z"></div> + </div> + <div class="columns"> + <div style="grid-column-start: z 1"></div> + </div> + <div class="columns"> + <div style="grid-column-start: z -1"></div> + </div> + <!-- Also non-existant, there is only one z --> + <div class="columns"> + <div style="grid-column-start: z 2"></div> + </div> + <div class="columns"> + <div style="grid-column-start: z -2"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-005-ref.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-005-ref.html new file mode 100644 index 00000000000..305d256fcbd --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-005-ref.html @@ -0,0 +1,161 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Grid Layout Test: auto-repeat with multiple tracks and line names reference</title> + <link rel="author" title="Mozilla" href="https://mozilla.org"> + <style> + .rows { + border: 1px solid black; + display: inline-grid; + grid-auto-flow: column; + grid-template-rows: [u v] 10px [w] 10px [x] 10px [y v] 10px [w] 10px [x] 10px [y z]; + grid-auto-columns: 5px; + grid-row-gap: 3px; + width: min-content; + /* Does not fit a whole-number of repetitions */ + height: 94px; + } + div > div { + background: blue; + } + </style> +</head> +<body> + <div class="rows"> + <div style="grid-row-start: u"></div> + </div> + <!-- Non-existant line name --> + <div class="rows"> + <div style="grid-row-start: nonesuch"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -1"></div> + </div> + <!-- Also non-existant, there is only one u --> + <div class="rows"> + <div style="grid-row-start: u 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -6"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -7"></div> + </div> + <!-- v --> + <div class="rows"> + <div style="grid-row-start: v"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v -2"></div> + </div> + <!-- Also non-existant, there are only two v's --> + <div class="rows"> + <div style="grid-row-start: v 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v -3"></div> + </div> + <!-- w --> + <div class="rows"> + <div style="grid-row-start: w"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w -2"></div> + </div> + <!-- Also non-existant, there are only two w's --> + <div class="rows"> + <div style="grid-row-start: w 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w -3"></div> + </div> + <!-- x --> + <div class="rows"> + <div style="grid-row-start: x"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x -2"></div> + </div> + <!-- Also non-existant, there are only two x's --> + <div class="rows"> + <div style="grid-row-start: x 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x -3"></div> + </div> + <!-- y --> + <div class="rows"> + <div style="grid-row-start: y"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y -2"></div> + </div> + <!-- Also non-existant, there are only two y's --> + <div class="rows"> + <div style="grid-row-start: y 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y -3"></div> + </div> + <!-- z --> + <div class="rows"> + <div style="grid-row-start: z"></div> + </div> + <div class="rows"> + <div style="grid-row-start: z 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: z -1"></div> + </div> + <!-- Also non-existant, there is only one z --> + <div class="rows"> + <div style="grid-row-start: z 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: z -2"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-005.html b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-005.html new file mode 100644 index 00000000000..8671df79b7c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-definition/grid-auto-repeat-multiple-values-005.html @@ -0,0 +1,163 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>CSS Grid Layout Test: auto-repeat with multiple tracks and line names</title> + <link rel="author" title="Mozilla" href="https://mozilla.org"> + <link rel="help" href="https://www.w3.org/TR/css-grid-1/#repeat-notation"> + <link rel="match" href="grid-auto-repeat-multiple-values-005-ref.html"> + <style> + .rows { + border: 1px solid black; + display: inline-grid; + grid-auto-flow: column; + grid-template-rows: [u] repeat(auto-fill, [v] 10px [w] 10px [x] 10px [y]) [z]; + grid-auto-columns: 5px; + grid-row-gap: 3px; + width: min-content; + /* Does not fit a whole-number of repetitions */ + height: 94px; + } + div > div { + background: blue; + } + </style> +</head> +<body> + <div class="rows"> + <div style="grid-row-start: u"></div> + </div> + <!-- Non-existant line name --> + <div class="rows"> + <div style="grid-row-start: nonesuch"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -1"></div> + </div> + <!-- Also non-existant, there is only one u --> + <div class="rows"> + <div style="grid-row-start: u 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -6"></div> + </div> + <div class="rows"> + <div style="grid-row-start: u -7"></div> + </div> + <!-- v --> + <div class="rows"> + <div style="grid-row-start: v"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v -2"></div> + </div> + <!-- Also non-existant, there are only two v's --> + <div class="rows"> + <div style="grid-row-start: v 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: v -3"></div> + </div> + <!-- w --> + <div class="rows"> + <div style="grid-row-start: w"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w -2"></div> + </div> + <!-- Also non-existant, there are only two w's --> + <div class="rows"> + <div style="grid-row-start: w 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: w -3"></div> + </div> + <!-- x --> + <div class="rows"> + <div style="grid-row-start: x"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x -2"></div> + </div> + <!-- Also non-existant, there are only two x's --> + <div class="rows"> + <div style="grid-row-start: x 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: x -3"></div> + </div> + <!-- y --> + <div class="rows"> + <div style="grid-row-start: y"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y -1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y -2"></div> + </div> + <!-- Also non-existant, there are only two y's --> + <div class="rows"> + <div style="grid-row-start: y 3"></div> + </div> + <div class="rows"> + <div style="grid-row-start: y -3"></div> + </div> + <!-- z --> + <div class="rows"> + <div style="grid-row-start: z"></div> + </div> + <div class="rows"> + <div style="grid-row-start: z 1"></div> + </div> + <div class="rows"> + <div style="grid-row-start: z -1"></div> + </div> + <!-- Also non-existant, there is only one z --> + <div class="rows"> + <div style="grid-row-start: z 2"></div> + </div> + <div class="rows"> + <div style="grid-row-start: z -2"></div> + </div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-item-percentage-quirk-001.html b/tests/wpt/web-platform-tests/css/css-grid/grid-item-percentage-quirk-001.html new file mode 100644 index 00000000000..0956b7a0253 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-item-percentage-quirk-001.html @@ -0,0 +1,9 @@ +<!-- quirks mode --> +<link rel="match" href="../../quirks/reference/green-100px-square-no-red.html"> +<link rel="help" href="https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk"> +<p>There should be a filled green square below, and no red.</p> +<div style="width: 100px; height: 100px; overflow: hidden; background: green;"> + <div style="display: grid; position: relative; left: -20px;"> + <canvas width=10 height=10 style="height: 200%; background: red;"></canvas> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/grid-item-percentage-quirk-002.html b/tests/wpt/web-platform-tests/css/css-grid/grid-item-percentage-quirk-002.html new file mode 100644 index 00000000000..f795cd096d9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/grid-item-percentage-quirk-002.html @@ -0,0 +1,9 @@ +<!-- quirks mode --> +<link rel="match" href="../../quirks/reference/green-100px-square-no-red.html"> +<link rel="help" href="https://quirks.spec.whatwg.org/#the-percentage-height-calculation-quirk"> +<p>There should be a filled green square below, and no red.</p> +<div style="width: 100px; height: 100px; overflow: hidden; background: green;"> + <div style="display: grid; position: relative; left: -20px;"> + <div width=10 height=10 style="height: 200%; width: 200px; background: red;"></div> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-002-ref.html b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-002-ref.html new file mode 100644 index 00000000000..88fdfa71f10 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-002-ref.html @@ -0,0 +1,27 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test Reference</title> +<style> +html,body { + color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0; +} + +div { + display: grid; + background-color: lime; +} + +table { + min-height: 100px; +} +</style> +<div> + <table> + <caption>Table caption</caption> + <thead> + <tr> + <th>This table should shrink after setting a smaller min-height</th> + </tr> + </thead> + </table> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-002.html b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-002.html new file mode 100644 index 00000000000..fdec4e6b3b4 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-002.html @@ -0,0 +1,46 @@ +<!doctype html> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<meta charset="utf-8"> +<title>Table grid items with min-height should shrink after setting a smaller min-height</title> +<link rel="author" href="mailto:mats@mozilla.com"> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="author" href="mailto:tlin@mozilla.com"> +<link rel="author" href="https://mozilla.org"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1527734"> +<link rel="match" href="table-grid-item-dynamic-002-ref.html"> +<style> +html,body { + color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0; +} + +div { + display: grid; + background-color: lime; +} + +table { + min-height: 500px; +} +</style> + +<div> + <table> + <caption>Table caption</caption> + <thead> + <tr> + <th>This table should shrink after setting a smaller min-height</th> + </tr> + </thead> + </table> +</div> +<script> +onload = function() { + let grid = document.querySelector("div"); + let table = grid.querySelector("table"); + grid.getBoundingClientRect(); + table.style.minHeight = "100px"; +} +</script> diff --git a/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-003-ref.html b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-003-ref.html new file mode 100644 index 00000000000..1de4ccea471 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-003-ref.html @@ -0,0 +1,30 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test Reference</title> +<style> +html,body { + color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0; +} + +div { + display: grid; + height: 100px; + background-color: lime; +} + +table { + padding-top: 100px; + height: 100%; + box-sizing: content-box; + background-color: yellow; +} +</style> +<div> + <table> + <thead> + <tr> + <th>Relayout shouldn't grow this table</th> + </tr> + </thead> + </table> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-003.html b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-003.html new file mode 100644 index 00000000000..32b809c5dcb --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-003.html @@ -0,0 +1,50 @@ +<!doctype html> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<meta charset="utf-8"> +<title>Table grid items with padding-top, percentage height, and box-sizing:content-box, don't grow on incremental relayout</title> +<link rel="author" href="mailto:mats@mozilla.com"> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="author" href="mailto:tlin@mozilla.com"> +<link rel="author" href="https://mozilla.org"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1521088"> +<link rel="match" href="table-grid-item-dynamic-003-ref.html"> +<style> +html,body { + color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0; +} + +div { + display: grid; + height: 100px; + background-color: lime; +} + +table { + padding-top: 100px; + height: 100%; + box-sizing: content-box; + background-color: yellow; +} +</style> + +<div> + <table> + <thead> + <tr> + <th>Relayout shouldn't grow this table</th> + </tr> + </thead> + </table> +</div> +<script> +onload = function() { + let grid = document.querySelector("div"); + grid.getBoundingClientRect(); + document.body.style.width = "50vw"; + grid.getBoundingClientRect(); + document.body.style.width = ""; +} +</script> diff --git a/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-004-ref.html b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-004-ref.html new file mode 100644 index 00000000000..9189fb5f7e2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-004-ref.html @@ -0,0 +1,31 @@ +<!doctype html> +<meta charset="utf-8"> +<title>CSS Test Reference</title> +<style> +html,body { + color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0; +} + +div { + display: grid; + height: 100px; + background-color: lime; +} + +table { + padding-top: 100px; + width: 500px; + height: 100%; + box-sizing: content-box; + background-color: yellow; +} +</style> +<div> + <table> + <thead> + <tr> + <th>Relayout shouldn't grow this table</th> + </tr> + </thead> + </table> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-004.html b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-004.html new file mode 100644 index 00000000000..537d85ad342 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-grid/table-grid-item-dynamic-004.html @@ -0,0 +1,51 @@ +<!doctype html> +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<meta charset="utf-8"> +<title>Table grid items with padding-top, definite width, percentage height, and box-sizing:content-box, don't grow on incremental relayout</title> +<link rel="author" href="mailto:mats@mozilla.com"> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="author" href="mailto:tlin@mozilla.com"> +<link rel="author" href="https://mozilla.org"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1521088"> +<link rel="match" href="table-grid-item-dynamic-004-ref.html"> +<style> +html,body { + color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0; +} + +div { + display: grid; + height: 100px; + background-color: lime; +} + +table { + padding-top: 100px; + width: 500px; + height: 100%; + box-sizing: content-box; + background-color: yellow; +} +</style> + +<div> + <table> + <thead> + <tr> + <th>Relayout shouldn't grow this table</th> + </tr> + </thead> + </table> +</div> +<script> +onload = function() { + let grid = document.querySelector("div"); + grid.getBoundingClientRect(); + document.body.style.width = "50vw"; + grid.getBoundingClientRect(); + document.body.style.width = ""; +} +</script> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/color-stop-currentcolor-ref.html b/tests/wpt/web-platform-tests/css/css-images/color-stop-currentcolor-ref.html index 7686a3b16ed..7686a3b16ed 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/color-stop-currentcolor-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/color-stop-currentcolor-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/color-stop-currentcolor.html b/tests/wpt/web-platform-tests/css/css-images/color-stop-currentcolor.html index e3074e398fe..e3074e398fe 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/color-stop-currentcolor.html +++ b/tests/wpt/web-platform-tests/css/css-images/color-stop-currentcolor.html diff --git a/tests/wpt/web-platform-tests/css/css-images/empty-background-image.html b/tests/wpt/web-platform-tests/css/css-images/empty-background-image.html new file mode 100644 index 00000000000..5192ff254c8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-images/empty-background-image.html @@ -0,0 +1,22 @@ +<!doctype html> +<title>Empty url shouldn't try to load a subresource.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://drafts.csswg.org/css-values-4/#url-empty"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1302991"> +<style> +@import url(); +@import url(''); +@import url(""); +</style> +<div style="background-image: url()"></div> +<div style="background-image: url('')"></div> +<div style='background-image: url("")'></div> +<script> +let t = async_test("Empty url shouldn't try to load a subresource."); +onload = t.step_func_done(function() { + for (let entry of performance.getEntriesByType("resource")) { + assert_not_equals(entry.name, location.href, "Shouldn't have tried to request ourselves as a subresource") + } +}); +</script> diff --git a/tests/wpt/web-platform-tests/css/css-images/gradient-nan-crash.html b/tests/wpt/web-platform-tests/css/css-images/gradient-nan-crash.html new file mode 100644 index 00000000000..8c4b6470425 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-images/gradient-nan-crash.html @@ -0,0 +1,2 @@ +<!doctype html> +<style>body { background: linear-gradient(black calc(0% * (1e39 - 1e39)), black 0%); }</style> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001-ref.html index b9f158093e7..b9f158093e7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001c.html index 4fb7f113897..4fb7f113897 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001e.html index 20a5f8ae560..20a5f8ae560 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001i.html index 6605be2e1a4..6605be2e1a4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001o.html index 36761156bb3..36761156bb3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001p.html index ac00ec44d1d..ac00ec44d1d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002-ref.html index 804bde94c1b..804bde94c1b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002c.html index 738c015a52e..738c015a52e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002e.html index 6132fdc27d6..6132fdc27d6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002i.html index 222ac9e7356..222ac9e7356 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002o.html index 6e0b555e57e..6e0b555e57e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002p.html index 97a150e582a..97a150e582a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-png-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-png-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001-ref.html index 84fa4d5ac18..84fa4d5ac18 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001e.html index a5dc815d03c..a5dc815d03c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001i.html index d2011bd6df4..d2011bd6df4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001o.html index 7bb8c640f43..7bb8c640f43 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001p.html index f6a2b73bf37..f6a2b73bf37 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002-ref.html index 3a8a5b542dd..3a8a5b542dd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002e.html index 5d4197fb374..5d4197fb374 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002i.html index 6d81d9b863c..6d81d9b863c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002o.html index 2d7b69ccc06..2d7b69ccc06 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002p.html index 74f43726ac2..74f43726ac2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003-ref.html index fde7f9fc974..fde7f9fc974 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003e.html index 9e4d758b055..9e4d758b055 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003i.html index 52693ee2148..52693ee2148 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003o.html index b1ff0dddbed..b1ff0dddbed 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003p.html index 3d7d02a5f2d..3d7d02a5f2d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-003p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-003p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004-ref.html index 25e768080be..25e768080be 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004e.html index 1d86232bca8..1d86232bca8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004i.html index ff54f41faaa..ff54f41faaa 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004o.html index 6a3015c8822..6a3015c8822 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004p.html index ec117017bfd..ec117017bfd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-004p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-004p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005-ref.html index c9089343c21..c9089343c21 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005e.html index ce25d2781d7..ce25d2781d7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005i.html index 10cef72c58f..10cef72c58f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005o.html index 781133d064f..781133d064f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005p.html index 67f99138c1c..67f99138c1c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-005p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-005p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006-ref.html index 21f9f2f29d7..21f9f2f29d7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006e.html index 850d0259a9c..850d0259a9c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006i.html index 536e639ece3..536e639ece3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006o.html index 42d07664aee..42d07664aee 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006p.html index 6a39673f260..6a39673f260 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-contain-svg-006p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-contain-svg-006p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001-ref.html index 96a1ec64b21..96a1ec64b21 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001c.html index 2a0071a01ce..2a0071a01ce 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001e.html index 1091e01b7aa..1091e01b7aa 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001i.html index 8db4683c274..8db4683c274 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001o.html index 8e40b532ca4..8e40b532ca4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001p.html index a75a977a37b..a75a977a37b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002-ref.html index 9dc589abeb6..9dc589abeb6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002c.html index 66836651fb4..66836651fb4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002e.html index 8dab30ccfd1..8dab30ccfd1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002i.html index 1866fde155d..1866fde155d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002o.html index a36ddbfce8e..a36ddbfce8e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002p.html index 36222054b03..36222054b03 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-png-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-png-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001-ref.html index b1539f92787..b1539f92787 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001e.html index c787be471f1..c787be471f1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001i.html index 972e20ac22b..972e20ac22b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001o.html index cf755657bc5..cf755657bc5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001p.html index db899db1b81..db899db1b81 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002-ref.html index 86706084f46..86706084f46 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002e.html index b42eb0eb796..b42eb0eb796 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002i.html index b79b532b737..b79b532b737 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002o.html index e888bd09428..e888bd09428 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002p.html index 65516da2ac1..65516da2ac1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003-ref.html index 18aa3624362..18aa3624362 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003e.html index d24d53cfd24..d24d53cfd24 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003i.html index 92c7a975a04..92c7a975a04 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003o.html index 66401ae1d1a..66401ae1d1a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003p.html index c726e733dcd..c726e733dcd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-003p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-003p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004-ref.html index cf0f747f22b..cf0f747f22b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004e.html index f253fdba681..f253fdba681 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004i.html index 0c52eaa1701..0c52eaa1701 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004o.html index 7bd2f8419e8..7bd2f8419e8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004p.html index da1c88c50b1..da1c88c50b1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-004p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-004p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005-ref.html index 374b0ac7e5f..374b0ac7e5f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005e.html index 4d142464814..4d142464814 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005i.html index 9948ea7878b..9948ea7878b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005o.html index 894b3e85aa7..894b3e85aa7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005p.html index 210be382ae2..210be382ae2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-005p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-005p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006-ref.html index 2e0ecc7d093..2e0ecc7d093 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006e.html index d699f2bb30a..d699f2bb30a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006i.html index f7c71eb8958..f7c71eb8958 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006o.html index f6b60a4cc75..f6b60a4cc75 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006p.html index 08c9acfe35a..08c9acfe35a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-cover-svg-006p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-cover-svg-006p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-001-ref.html index 138f0acbf03..138f0acbf03 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-001.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-001.html index dafadf2e58b..dafadf2e58b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-001.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-001.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-002-ref.html index ddddccad325..ddddccad325 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-002.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-002.html index 26ae89e4794..26ae89e4794 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-dyn-aspect-ratio-002.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-dyn-aspect-ratio-002.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001-ref.html index 19a03a19e47..19a03a19e47 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001c.html index 36031175e63..36031175e63 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001e.html index 08aa6b23fe2..08aa6b23fe2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001i.html index 90692c30cb5..90692c30cb5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001o.html index b57528b125d..b57528b125d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001p.html index 1e30e7aef4c..1e30e7aef4c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002-ref.html index b05469cf6e3..b05469cf6e3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002c.html index a332c37f4f0..a332c37f4f0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002e.html index d5903c2004d..d5903c2004d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002i.html index b5abd3d8001..b5abd3d8001 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002o.html index a8904f2a4ed..a8904f2a4ed 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002p.html index e0b44135ec9..e0b44135ec9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-png-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-png-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001-ref.html index afb32785ad3..afb32785ad3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001e.html index 008f8da98e3..008f8da98e3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001i.html index 43e5ae7e86c..43e5ae7e86c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001o.html index 91e95540044..91e95540044 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001p.html index d2ffffdb4a1..d2ffffdb4a1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002-ref.html index 13c4d84030c..13c4d84030c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002e.html index 38f3c10c1f0..38f3c10c1f0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002i.html index 2bbf505e48e..2bbf505e48e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002o.html index 8580cdb4cf5..8580cdb4cf5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002p.html index 4e1d61a140a..4e1d61a140a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003-ref.html index ab27b3d717b..ab27b3d717b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003e.html index 37182514dc7..37182514dc7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003i.html index 54e55f4e849..54e55f4e849 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003o.html index 1f640d990d7..1f640d990d7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003p.html index 165f072bc6f..165f072bc6f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-003p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-003p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004-ref.html index 70c9487960b..70c9487960b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004e.html index 3d36a3b97ba..3d36a3b97ba 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004i.html index cd83227eb7f..cd83227eb7f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004o.html index 10140225cfa..10140225cfa 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004p.html index bd54333a4b4..bd54333a4b4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-004p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-004p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005-ref.html index c8e4ce1e7d5..c8e4ce1e7d5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005e.html index c706c670e87..c706c670e87 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005i.html index a12265f224d..a12265f224d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005o.html index d25f0ecf1ef..d25f0ecf1ef 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005p.html index 1bbd6a9b89e..1bbd6a9b89e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-005p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-005p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006-ref.html index 85883fe9dcd..85883fe9dcd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006e.html index c741c7e6589..c741c7e6589 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006i.html index 6da7334e56f..6da7334e56f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006o.html index 7d48d6f216a..7d48d6f216a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006p.html index 73b4f72089d..73b4f72089d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-fill-svg-006p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-fill-svg-006p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001-ref.html index bd25ed754e0..bd25ed754e0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001c.html index fe483baa963..fe483baa963 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001e.html index 91be966a408..91be966a408 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001i.html index 39d7e475c3b..39d7e475c3b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001o.html index f1d702a5c64..f1d702a5c64 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001p.html index 85edee90804..85edee90804 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002-ref.html index 8273d3b21e5..8273d3b21e5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002c.html index 09bc836d024..09bc836d024 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002e.html index ce3e07e7df5..ce3e07e7df5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002i.html index 1f33b5758ac..1f33b5758ac 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002o.html index 2b1083628e5..2b1083628e5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002p.html index 656515ddebd..656515ddebd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-png-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-png-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001-ref.html index 3332452df77..3332452df77 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001e.html index 31365eea558..31365eea558 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001i.html index 7c47f68f64e..7c47f68f64e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001o.html index 24aa6cc6e32..24aa6cc6e32 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001p.html index f2b3dc05c78..f2b3dc05c78 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002-ref.html index 0e48003ec0f..0e48003ec0f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002e.html index 68cd20a9e40..68cd20a9e40 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002i.html index 5fb1823d479..5fb1823d479 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002o.html index 7d0ad60340e..7d0ad60340e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002p.html index f383450dbda..f383450dbda 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003-ref.html index 33a01176fa6..33a01176fa6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003e.html index 4db5bcc86eb..4db5bcc86eb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003i.html index 132687ca8ee..132687ca8ee 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003o.html index 775bbc60297..775bbc60297 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003p.html index 211e9132bd1..211e9132bd1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-003p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-003p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004-ref.html index 757708cacce..757708cacce 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004e.html index d998995ac94..d998995ac94 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004i.html index 6b5564a0f0e..6b5564a0f0e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004o.html index 26c0dd837fe..26c0dd837fe 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004p.html index 2d8d8d5cb85..2d8d8d5cb85 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-004p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-004p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005-ref.html index c8d70e4e0b6..c8d70e4e0b6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005e.html index 328a337d3f8..328a337d3f8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005i.html index d452e38a0d5..d452e38a0d5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005o.html index 7fd4e5b3976..7fd4e5b3976 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005p.html index 05aab2098f1..05aab2098f1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-005p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-005p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006-ref.html index 016ef696159..016ef696159 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006e.html index a91251704db..a91251704db 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006i.html index 15bb00bebd1..15bb00bebd1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006o.html index da2890aa01f..da2890aa01f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006p.html index 8b8b42135c6..8b8b42135c6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-none-svg-006p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-none-svg-006p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001-ref.html index abf3788be2a..abf3788be2a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001c.html index 5b8116ecbcb..5b8116ecbcb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001e.html index c2f587405fd..c2f587405fd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001i.html index 0fd12092459..0fd12092459 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001o.html index bed477d0481..bed477d0481 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001p.html index ff152315ff9..ff152315ff9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002-ref.html index 3e516985f8c..3e516985f8c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002c.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002c.html index 626f379f1c7..626f379f1c7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002e.html index 4190c5f1f38..4190c5f1f38 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002i.html index dc3fcc2a41a..dc3fcc2a41a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002o.html index f7dcca2ecc5..f7dcca2ecc5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002p.html index 6d05ee0b254..6d05ee0b254 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-png-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-png-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001-ref.html index 1fad02c9117..1fad02c9117 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001e.html index a6bc8d19f26..a6bc8d19f26 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001i.html index d331a9e5595..d331a9e5595 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001o.html index 88ba8171f63..88ba8171f63 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001p.html index ab29b635cc0..ab29b635cc0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002-ref.html index 03d4f4c939b..03d4f4c939b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002e.html index a0b7f1180c6..a0b7f1180c6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002i.html index 6d260fb16c6..6d260fb16c6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002o.html index d60a5a92a9d..d60a5a92a9d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002p.html index 8cca87a44d3..8cca87a44d3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003-ref.html index 18883dc9a35..18883dc9a35 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003e.html index f4d7cf98b70..f4d7cf98b70 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003i.html index 1841ec75063..1841ec75063 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003o.html index 31f73c3ba85..31f73c3ba85 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003p.html index ee3ff8e15d8..ee3ff8e15d8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-003p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-003p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004-ref.html index 126b62ed344..126b62ed344 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004e.html index 3f83b62dce0..3f83b62dce0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004i.html index ecc6337c23e..ecc6337c23e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004o.html index 9dad7088f58..9dad7088f58 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004p.html index 7993375e7d4..7993375e7d4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-004p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-004p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005-ref.html index a107fb46416..a107fb46416 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005e.html index 2e813e7d2c7..2e813e7d2c7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005i.html index a741f643318..a741f643318 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005o.html index 808952b89a3..808952b89a3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005p.html index f0b7632b84d..f0b7632b84d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-005p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-005p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006-ref.html index 4a06d37b5ca..4a06d37b5ca 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006e.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006e.html index 15ce6d3d4da..15ce6d3d4da 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006i.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006i.html index 83c544ed134..83c544ed134 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006o.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006o.html index 00eae1ca34b..00eae1ca34b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006p.html b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006p.html index f5e894034ea..f5e894034ea 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-fit-scale-down-svg-006p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-fit-scale-down-svg-006p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001-ref.html index 1027cfc35ca..1027cfc35ca 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001c.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001c.html index 20ff1cb935c..20ff1cb935c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001e.html index cdf35a1fc7a..cdf35a1fc7a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001i.html index d3e2622b406..d3e2622b406 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001o.html index cdf0e0b5302..cdf0e0b5302 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001p.html index 8b80b9c5bf3..8b80b9c5bf3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002-ref.html index bd14f9cfc57..bd14f9cfc57 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002c.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002c.html index 5ad030ffb60..5ad030ffb60 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002c.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002c.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002e.html index 749948ae4c4..749948ae4c4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002i.html index 88b4a150013..88b4a150013 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002o.html index 0d507900f82..0d507900f82 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002p.html index 00ace782bc3..00ace782bc3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-png-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-png-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001-ref.html index 61c386d88f6..61c386d88f6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001e.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001e.html index 05f226cd887..05f226cd887 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001i.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001i.html index eecf0f4330c..eecf0f4330c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001o.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001o.html index 589999c91f1..589999c91f1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001p.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001p.html index 8b0b468b2fd..8b0b468b2fd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-001p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-001p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002-ref.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002-ref.html index d75e5edb7cb..d75e5edb7cb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002e.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002e.html index 0bde50d0ca6..0bde50d0ca6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002e.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002e.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002i.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002i.html index 202b171c3a5..202b171c3a5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002i.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002i.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002o.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002o.html index e05938167c4..e05938167c4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002o.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002o.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002p.html b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002p.html index 3c6b7a3d400..3c6b7a3d400 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/object-position-svg-002p.html +++ b/tests/wpt/web-platform-tests/css/css-images/object-position-svg-002p.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8-noSize.svg b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8-noSize.svg index db715d875ee..db715d875ee 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8-noSize.svg +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8-noSize.svg diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8-parDefault.svg b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8-parDefault.svg index 1b0bca07375..1b0bca07375 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8-parDefault.svg +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8-parDefault.svg diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8.png b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8.png Binary files differindex bd238458713..bd238458713 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8.png +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8.svg b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8.svg index 08e36594026..08e36594026 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-16x8.svg +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-16x8.svg diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16-noSize.svg b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16-noSize.svg index e741537b930..e741537b930 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16-noSize.svg +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16-noSize.svg diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16-parDefault.svg b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16-parDefault.svg index ec8c59dcbdb..ec8c59dcbdb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16-parDefault.svg +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16-parDefault.svg diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16.png b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16.png Binary files differindex 596fdb389d6..596fdb389d6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16.png +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16.png diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16.svg b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16.svg index c336e3af1bd..c336e3af1bd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/colors-8x16.svg +++ b/tests/wpt/web-platform-tests/css/css-images/support/colors-8x16.svg diff --git a/tests/wpt/web-platform-tests/css/css-images/support/generate-object-fit-and-position-canvas-tests.sh b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-fit-and-position-canvas-tests.sh new file mode 100644 index 00000000000..aeeee5284cd --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-fit-and-position-canvas-tests.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ +# +# Script to generate <canvas src> reftest files for "object-fit" and +# "object-position", from corresponding reftest files that use <object>. +# +# This script expects to be run from the parent directory. + +# Array of image files that we'll use +imageFileArr=("support/colors-16x8.png" "support/colors-8x16.png") +canvasAttributeArr=('width="16" height="8"' 'width="8" height="16"') +numImageFiles=${#imageFileArr[@]} + + +for ((i = 0; i < $numImageFiles; i++)); do + + imageFile=${imageFileArr[$i]} + canvasAttrs=${canvasAttributeArr[$i]} + + # Loop across <object> tests: + # (We assume that tests that end with "001" use the first PNG image in + # $imageFileArr, and tests that end with "002" use the second PNG image.) + let testNum=$i+1 + for origTestName in object-*-png-*00${testNum}o.html; do + # Find the corresponding reference case: + origReferenceName=$(echo $origTestName | + sed "s/o.html/-ref.html/") + + # Replace "o" suffix in filename with "c" (for "canvas") + canvasTestName=$(echo $origTestName | + sed "s/o.html/c.html/") + + # Generate testcase + # (converting <object data="..."> to <canvas width="..." height="..."> + echo "Generating $canvasTestName from $origTestName." + hg cp $origTestName $canvasTestName + + # Do string-replacements in testcase to convert it to test canvas: + # Adjust html & body nodes: + sed -i "s|<html>|<html class=\"reftest-wait\">|" $canvasTestName + sed -i "s|<body>|<body onload=\"drawImageToCanvases('$imageFile')\">|" $canvasTestName + # Adjust <title>: + sed -i "s|object element|canvas element|g" $canvasTestName + # Tweak the actual tags (open & close tags, and CSS rule): + sed -i "s|object {|canvas {|" $canvasTestName + sed -i "s|<object|<canvas|" $canvasTestName + sed -i "s|</object>|</canvas>|" $canvasTestName + # Drop "data" attr (pointing to image URI) and replace with + # width/height attrs to establish the canvas's intrinsic size: + sed -i "s|data=\"$imageFile\"|$canvasAttrs|" $canvasTestName + + # Add a <script> block to draw an image into each canvas: + sed -i "/<\/style>/a \\ + <script>\n\ + function drawImageToCanvases(imageURI) {\n\ + var image = new Image();\n\ + image.onload = function() {\n\ + var canvasElems = document.getElementsByTagName(\"canvas\");\n\ + for (var i = 0; i < canvasElems.length; i++) {\n\ + var ctx = canvasElems[i].getContext(\"2d\");\n\ + ctx.drawImage(image, 0, 0);\n\ + }\n\ + document.documentElement.removeAttribute(\"class\");\n\ + }\n\ + image.src = imageURI;\n\ + }\n\ + <\/script>" $canvasTestName + done +done diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-fit-png-tests.sh b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-fit-png-tests.sh index af20d0212ad..af20d0212ad 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-fit-png-tests.sh +++ b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-fit-png-tests.sh diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-fit-svg-tests.sh b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-fit-svg-tests.sh index c4d51877e0e..c4d51877e0e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-fit-svg-tests.sh +++ b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-fit-svg-tests.sh diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-position-png-tests.sh b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-position-png-tests.sh index 4763fabf7ff..4763fabf7ff 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-position-png-tests.sh +++ b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-position-png-tests.sh diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-position-svg-tests.sh b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-position-svg-tests.sh index e00385a4748..e00385a4748 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-position-svg-tests.sh +++ b/tests/wpt/web-platform-tests/css/css-images/support/generate-object-position-svg-tests.sh diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-fit-ref.html b/tests/wpt/web-platform-tests/css/css-images/support/template-object-fit-ref.html index 068c74b4e41..068c74b4e41 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-fit-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/support/template-object-fit-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-fit-test.html b/tests/wpt/web-platform-tests/css/css-images/support/template-object-fit-test.html index 8ec4664db92..8ec4664db92 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-fit-test.html +++ b/tests/wpt/web-platform-tests/css/css-images/support/template-object-fit-test.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-position-ref.html b/tests/wpt/web-platform-tests/css/css-images/support/template-object-position-ref.html index 19661f41f6f..19661f41f6f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-position-ref.html +++ b/tests/wpt/web-platform-tests/css/css-images/support/template-object-position-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-position-test.html b/tests/wpt/web-platform-tests/css/css-images/support/template-object-position-test.html index fb4b3ad3c7a..fb4b3ad3c7a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/template-object-position-test.html +++ b/tests/wpt/web-platform-tests/css/css-images/support/template-object-position-test.html diff --git a/tests/wpt/web-platform-tests/css/css-layout-api/multicol-in-item-crash.https.html b/tests/wpt/web-platform-tests/css/css-layout-api/multicol-in-item-crash.https.html new file mode 100644 index 00000000000..b7d481e1fb7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-layout-api/multicol-in-item-crash.https.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1178979"> +<math><ms><span><span style="columns:3;"> diff --git a/tests/wpt/web-platform-tests/css/css-paint-api/paint2d-gradient.https.html b/tests/wpt/web-platform-tests/css/css-paint-api/paint2d-gradient.https.html index 594f42b4c09..f99b7cb9011 100644 --- a/tests/wpt/web-platform-tests/css/css-paint-api/paint2d-gradient.https.html +++ b/tests/wpt/web-platform-tests/css/css-paint-api/paint2d-gradient.https.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <html class="reftest-wait"> +<meta name=fuzzy content="0-5;3000-4000"> <link rel="help" href="https://drafts.css-houdini.org/css-paint-api/"> <link rel="match" href="paint2d-gradient-ref.html"> <style> @@ -39,4 +40,3 @@ registerPaint('gradients', class { </script> </body> </html> - diff --git a/tests/wpt/web-platform-tests/css/css-pseudo/grammar-spelling-errors-001.html b/tests/wpt/web-platform-tests/css/css-pseudo/grammar-spelling-errors-001.html new file mode 100644 index 00000000000..f97d898e9a8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-pseudo/grammar-spelling-errors-001.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> + + <html lang="en"> + + <meta charset="UTF-8"> + + <title>CSS Pseudo-Elements Test: ::spelling-error overlay drawn over the ::grammar-error overlay</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="https://www.w3.org/TR/css-pseudo-4/#highlight-painting"> + <link rel="match" href="reference/grammar-spelling-errors-001-ref.html"> + + <meta content="" name="flags"> + <meta name="assert" content="In this test, we postulate that the word 'dificultly' represents both a grammar error and a spelling error. In such editorial scenario, then the pseudo-element ::spelling-error's background color (yellow) is supposed to be drawn over the pseudo-element ::grammar-error's background color (red). The color should remain green as the pseudo-element ::spelling-error's 'color' is unspecified."> + + <style> + div + { + font-size: 300%; + } + + div::spelling-error + { + background-color: yellow; + } + + div::grammar-error + { + background-color: red; + color: green; + } + </style> + + <script type="text/javascript"> + function startTest() + { + document.getElementById("target").focus(); + } + </script> + + <body onload="startTest();"> + + <p>PREREQUISITE: User agent needs to have an enabled and capable grammar error module and an enabled and capable spelling error module. If it does not, then this test does not apply to such user agent. + + <p>This test also requires that the targeted editable text element must receive system focus. + + <p>Test passes if each glyph of "dificultly" is green and if the background of "dificultly" is yellow. + + <div contenteditable="true" id="target">This is dificultly.</div> diff --git a/tests/wpt/web-platform-tests/css/css-pseudo/grammar-spelling-errors-002.html b/tests/wpt/web-platform-tests/css/css-pseudo/grammar-spelling-errors-002.html new file mode 100644 index 00000000000..242a3af9afb --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-pseudo/grammar-spelling-errors-002.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> + + <html lang="en"> + + <meta charset="UTF-8"> + + <title>CSS Pseudo-Elements Test: ::spelling-error overlay drawn over the ::grammar-error overlay</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + <link rel="help" href="https://www.w3.org/TR/css-pseudo-4/#highlight-painting"> + <link rel="match" href="reference/grammar-spelling-errors-002-ref.html"> + + <meta content="" name="flags"> + <meta name="assert" content="In this test, we postulate that the word 'wolks' represents both a grammar error and a spelling error. In such such editorial scenario, then the pseudo-element ::spelling-error's 'color' (green) is supposed to be drawn over the pseudo-element ::grammar-error's 'color' (red). The background color should remain yellow as ::spelling-error's 'background-color' is unspecified, therefore defaulting to 'transparent'."> + + <style> + div + { + font-size: 300%; + } + + div::spelling-error + { + color: green; + } + + div::grammar-error + { + background-color: yellow; + color: red; + } + </style> + + <script type="text/javascript"> + function startTest() + { + document.getElementById("target").focus(); + } + </script> + + <body onload="startTest();"> + + <p>PREREQUISITE: User agent needs to have an enabled and capable grammar error module and an enabled and capable spelling error module. If it does not, then this test does not apply to such user agent. + + <p>This test also requires that the targeted editable text element must receive system focus. + + <p>Test passes if each glyph of "wolks" is green and if the background of "wolks" is yellow. + + <div contenteditable="true" id="target">I wolks on the sidewalk.</div> diff --git a/tests/wpt/web-platform-tests/css/css-pseudo/reference/grammar-spelling-errors-001-ref.html b/tests/wpt/web-platform-tests/css/css-pseudo/reference/grammar-spelling-errors-001-ref.html new file mode 100644 index 00000000000..48498e688f2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-pseudo/reference/grammar-spelling-errors-001-ref.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> + + <html lang="en"> + + <meta charset="UTF-8"> + + <title>CSS Reftest Reference</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + + <style> + div + { + font-size: 300%; + } + + span + { + background-color: yellow; + color: green; + } + </style> + + <script type="text/javascript"> + function startTest() + { + document.getElementById("target").focus(); + } + </script> + + <body onload="startTest();"> + + <p>PREREQUISITE: User agent needs to have an enabled and capable grammar error module and an enabled and capable spelling error module. If it does not, then this test does not apply to such user agent. + + <p>This test also requires that the targeted editable text element must receive system focus. + + <p>Test passes if each glyph of "dificultly" is green and if the background of "dificultly" is yellow. + + <div contenteditable="true" id="target">This is <span>dificultly</span>.</div> diff --git a/tests/wpt/web-platform-tests/css/css-pseudo/reference/grammar-spelling-errors-002-ref.html b/tests/wpt/web-platform-tests/css/css-pseudo/reference/grammar-spelling-errors-002-ref.html new file mode 100644 index 00000000000..b3c89177591 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-pseudo/reference/grammar-spelling-errors-002-ref.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> + + <html lang="en"> + + <meta charset="UTF-8"> + + <title>CSS Reftest Reference</title> + + <link rel="author" title="Gérard Talbot" href="http://www.gtalbot.org/BrowserBugsSection/css21testsuite/"> + + <style> + div + { + font-size: 300%; + } + + span + { + background-color: yellow; + color: green; + } + </style> + + <script type="text/javascript"> + function startTest() + { + document.getElementById("target").focus(); + } + </script> + + <body onload="startTest();"> + + <p>PREREQUISITE: User agent needs to have an enabled and capable grammar error module and an enabled and capable spelling error module. If it does not, then this test does not apply to such user agent. + + <p>This test also requires that the targeted editable text element must receive system focus. + + <p>Test passes if each glyph of "wolks" is green and if the background of "wolks" is yellow. + + <div contenteditable="true" id="target">I <span>wolks</span> on the sidewalk.</div> diff --git a/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-after-relayout/resnap-to-focused.html b/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-after-relayout/resnap-to-focused.html new file mode 100644 index 00000000000..637c578a853 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-after-relayout/resnap-to-focused.html @@ -0,0 +1,82 @@ +<!doctype html> +<title>Resnap to focused element after relayout</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +#snapper { + counter-reset: child 0; + width: 200px; + scroll-snap-type: block mandatory; + overflow:hidden; + height: 100px; +} +.child { + width: 100px; + height: 100px; + background:red; + text-align: center; + box-sizing: border-box; + counter-increment: child; + float: left; +} +.child.f { + background: green; + scroll-snap-align: center; +} +.child::before { + content: counter(child); +} + +</style> + +<link rel=author title="Tab Atkins-Bittner" href="https://www.xanthir.com/contact/"> +<link rel="help" href="https://drafts.csswg.org/css-scroll-snap/#re-snap"> +<!-- +When re-snapping after a layout change, +if multiple elements were capable of being the snap target previously, +and one of them is focused, +you must resnap to the focused one. +--> +<div id=snapper> + <div class="child no-snap" tabindex=-1></div> + <div class=child></div> + <div class="child f" tabindex=-1></div> + <div class="child f" tabindex=-1></div> + <div class=child></div> + <div class=child></div> +</div> + +<script> + +var container = document.querySelector("#snapper"); +var [one,two] = document.querySelectorAll(".child.f"); +var unsnappable = document.querySelector(".child.no-snap"); + +async_test(t=>{ + requestAnimationFrame(()=>{ + testSnap(t, one, 3); + requestAnimationFrame(()=>{ + testSnap(t, two, 4); + requestAnimationFrame(()=>{ + testSnap(t, one, 3); + t.done(); + }); + }); + }); +}); + +function testSnap(t, child, expectedRow) { + t.step(()=>{ + unsnappable.focus(); + container.style.width = "200px"; + var startingRow = container.scrollTop/100 + 1; + assert_equals(startingRow, 2, "Initially snapped to row 2"); + child.focus(); + container.style.width = "100px"; + var endingRow = container.scrollTop/100 + 1; + assert_equals(endingRow, expectedRow, `After resize, should snap to row ${expectedRow}.`); + }); +} + +</script> diff --git a/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-at-user-scroll-end-manual.html b/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-at-user-scroll-end-manual.html deleted file mode 100644 index 373e3fcb1e1..00000000000 --- a/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-at-user-scroll-end-manual.html +++ /dev/null @@ -1,90 +0,0 @@ -<!DOCTYPE html> -<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-type" /> -<title>Tests that window should snap at user scroll end.</title> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<style> -html { - margin: 0px; - scroll-snap-type: both mandatory; -} -#content { - width: 2000px; - height: 2000px; - padding: 0px; - margin: 0px; -} -#target { - position: relative; - left: 400px; - top: 400px; - width: 400px; - height: 400px; - background-color: lightblue; - overflow: hidden; - scroll-snap-align: start; -} -#i1 { - color: red; - font-weight: bold; -} -</style> - -<div id="content"> - <div id="target"> - <h1>CSSScrollSnap</h1> - <h4>Tests that the window can snap at user scroll end.</h4> - <ol> - <li id="i1" style="color: red"> - Scroll the page vertically and horizontally. - Keep scrolling until background has turned yellow.</li> - <li id="i2"> Press the button "Done"</li> - </ol> - <input type="button" id="btn" value="DONE" style="width: 100px; height: 50px;"/> - </div> -</div> - -<script> -var snap_test = async_test('Tests that window should snap at user scroll end.'); -var body = document.body; -var button = document.getElementById("btn"); -var target = document.getElementById("target"); -var instruction1 = document.getElementById("i1"); -var instruction2 = document.getElementById("i2"); -var scrolled_x = false; -var scrolled_y = false; -var start_x = window.scrollX; -var start_y = window.scrollY; - -window.onscroll = function() { - if (scrolled_x && scrolled_y) { - body.style.backgroundColor = "yellow"; - instruction1.style.color = "black"; - instruction1.style.fontWeight = "normal"; - instruction2.style.color = "red"; - instruction2.style.fontWeight = "bold"; - return; - } - - scrolled_x |= window.scrollX != start_x; - scrolled_y |= window.scrollY != start_y; -} - -button.onclick = function() { - if (!scrolled_x || !scrolled_y) - return; - - snap_test.step(() => { - assert_equals(window.scrollX, target.offsetLeft, - "window.scrollX should be at snapped position."); - assert_equals(window.scrollY, target.offsetTop, - "window.scrollY should be at snapped position."); - }); - - // To make the test result visible. - var content = document.getElementById("content"); - body.removeChild(content); - snap_test.done(); -} - -</script> diff --git a/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-at-user-scroll-end.html b/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-at-user-scroll-end.html new file mode 100644 index 00000000000..8643b3c1486 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-scroll-snap/snap-at-user-scroll-end.html @@ -0,0 +1,111 @@ +<!DOCTYPE html> +<link rel="help" href="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-type" /> +<title>Tests that window should snap at user scroll end.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="support/common.js"></script> +<style> +html { + margin: 0px; + scroll-snap-type: both mandatory; +} +#content { + width: 2000px; + height: 2000px; + padding: 0px; + margin: 0px; +} +#target { + position: relative; + left: 400px; + top: 400px; + width: 400px; + height: 400px; + background-color: lightblue; + overflow: hidden; + scroll-snap-align: start; +} +#i1 { + color: red; + font-weight: bold; +} +</style> + +<div id="content"> + <div id="target"> + <h1>CSSScrollSnap</h1> + <h4>Tests that the window can snap at user scroll end.</h4> + <ol> + <li id="i1" style="color: red"> + Scroll the page vertically and horizontally. + Keep scrolling until background has turned yellow.</li> + <li id="i2"> Press the button "Done"</li> + </ol> + <input type="button" id="btn" value="DONE" style="width: 100px; height: 50px;"/> + </div> +</div> + +<script> +var snap_test = async_test('Tests that window should snap at user scroll end.'); +var body = document.body; +var button = document.getElementById("btn"); +var target = document.getElementById("target"); +var instruction1 = document.getElementById("i1"); +var instruction2 = document.getElementById("i2"); +var scrolled_x = false; +var scrolled_y = false; +var start_x = window.scrollX; +var start_y = window.scrollY; +var actions_promise; + +scrollTop = () => window.scrollY; + +window.onscroll = function() { + if (scrolled_x && scrolled_y) { + body.style.backgroundColor = "yellow"; + instruction1.style.color = "black"; + instruction1.style.fontWeight = "normal"; + instruction2.style.color = "red"; + instruction2.style.fontWeight = "bold"; + return; + } + + scrolled_x |= window.scrollX != start_x; + scrolled_y |= window.scrollY != start_y; +} + +button.onclick = function() { + if (!scrolled_x || !scrolled_y) + return; + + snap_test.step(() => { + assert_equals(window.scrollX, target.offsetLeft, + "window.scrollX should be at snapped position."); + assert_equals(window.scrollY, target.offsetTop, + "window.scrollY should be at snapped position."); + }); + + // To make the test result visible. + var content = document.getElementById("content"); + body.removeChild(content); + actions_promise.then( () => { + snap_test.done(); + }); +} + +// Inject scroll actions. +const pos_x = 20; +const pos_y = 20; +const scroll_delta_x = 100; +const scroll_delta_y = 100; +actions_promise = new test_driver.Actions() + .scroll(pos_x, pos_y, scroll_delta_x, scroll_delta_y) + .send().then(() => { + return waitForAnimationEnd(scrollTop); +}).then(() => { + return test_driver.click(button); +}); +</script> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-015.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-015.html new file mode 100644 index 00000000000..4af5fa3bfff --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-015.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: grid track inline size should respect aspect-ratio and box-sizing</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org/"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-track-sizing"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> +<style> + #reference-overlapped-red { + position: absolute; + background-color: red; + width: 100px; + height: 100px; + z-index: -1; + } +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<div id="reference-overlapped-red"></div> +<div style="display: grid; grid-template-columns: auto; width: max-content; background: green;"> + <div style="height: 100px; aspect-ratio: 1/1; padding-top: 50px; box-sizing: border-box;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-016.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-016.html new file mode 100644 index 00000000000..e1c92908f07 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-016.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: grid track size should respect aspect-ratio when using + intrinsic size keywords in grid items</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org/"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-track-sizing"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> +<style> + #reference-overlapped-red { + position: absolute; + background-color: red; + width: 100px; + height: 100px; + z-index: -1; + } +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<div id="reference-overlapped-red"></div> +<div style="display: grid; grid-template-columns: auto; width: min-content; background: green;"> + <div style="height: 50px; width: min-content; aspect-ratio: 2/1;"></div> + <div style="height: 50px; width: 1%; min-width: min-content; aspect-ratio: 2/1;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-017.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-017.html new file mode 100644 index 00000000000..7a09957d5b7 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/grid-aspect-ratio-017.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: grid track size should respect aspect-ratio when using + intrinsic size keywords in grid items with orthogonal writing mode</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org/"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-track-sizing"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> +<style> + #reference-overlapped-red { + position: absolute; + background-color: red; + width: 100px; + height: 100px; + z-index: -1; + } + .item { + aspect-ratio: 2/1; + writing-mode: vertical-lr; + } +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<div id="reference-overlapped-red"></div> +<div style="display: grid; grid-template-columns: auto; width: min-content; background: green;"> + <div class="item" style="width: 100px; height: min-content;"></div> + <div class="item" style="width: 100px; height: 1%; min-height: min-content;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-012.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-012.html new file mode 100644 index 00000000000..5040da5ca26 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-012.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: min-content size contribution with border-box</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org/"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<div style="width: min-content; height: 25px; background: green;"> + <div style="height: 25px; aspect-ratio: 4/1; box-sizing: border-box; padding-top: 15px;"></div> +</div> + +<div style="width: min-content; height: 25px; background: green;"> + <div style="height: 10px; aspect-ratio: 4/1; min-height: 25px; box-sizing: border-box; padding-top: 15px;"></div> +</div> + +<div style="width: min-content; height: 25px; background: green;"> + <div style="height: 100px; aspect-ratio: 4/1; max-height: 25px; box-sizing: border-box; padding-top: 15px;"></div> +</div> + +<div style="width: min-content; height: 25px; background: green;"> + <div style="height: 40px; aspect-ratio: auto 4/1; box-sizing: border-box; padding-top: 15px;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-013.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-013.html new file mode 100644 index 00000000000..48a1d12a7e4 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-013.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: max-content size contribution with border-box</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org/"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<div style="width: max-content; height: 25px; background: green;"> + <div style="height: 25px; aspect-ratio: 4/1; box-sizing: border-box; padding-top: 25px;"></div> +</div> + +<div style="width: max-content; height: 25px; background: green;"> + <div style="height: 10px; aspect-ratio: 4/1; min-height: 25px; box-sizing: border-box; padding-top: 15px;"></div> +</div> + +<div style="width: max-content; height: 25px; background: green;"> + <div style="height: 100px; aspect-ratio: 4/1; max-height: 25px; box-sizing: border-box; padding-top: 15px;"></div> +</div> + +<div style="width: max-content; height: 25px; background: green;"> + <div style="height: 40px; aspect-ratio: auto 4/1; box-sizing: border-box; padding-top: 15px;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-014.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-014.html new file mode 100644 index 00000000000..c9308df630a --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-014.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: min-content size keyword together with min-content size contribution</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org/"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5032"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<div style="width: min-content; background: green;"> + <div style="width: min-content; height: 100px; aspect-ratio: 1/1;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-015.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-015.html new file mode 100644 index 00000000000..c279413da1d --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-015.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: max-content size keyword together with min-content size contribution</title> +<link rel="author" title="Mozilla" href="https://www.mozilla.org/"> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/5032"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<div style="width: max-content; background: green;"> + <div style="width: min-content; height: 100px; aspect-ratio: 1/1;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-016.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-016.html new file mode 100644 index 00000000000..61a77af130e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/intrinsic-size-016.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: min-size:auto and non auto/min-content/max-content width</title> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> +<style> +.target { + background: green; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<!-- `min-width:auto` does not take the content size into account if the + aspect-ratio is not used for sizing. --> +<div class="target" style="width: 100px; height: 100px; aspect-ratio: 1/1;"> + <div style="width: 200px;"></div> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/parsing/aspect-ratio-invalid.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/parsing/aspect-ratio-invalid.html index d85df5a8448..c508c9ed8a4 100644 --- a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/parsing/aspect-ratio-invalid.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/parsing/aspect-ratio-invalid.html @@ -16,4 +16,7 @@ test_invalid_value("aspect-ratio", "16 / -9"); test_invalid_value("aspect-ratio", "1 invalid"); test_invalid_value("aspect-ratio", "invalid 1.5"); test_invalid_value("aspect-ratio", "auto 1 / 1 auto"); +test_invalid_value("aspect-ratio", "16 /"); +test_invalid_value("aspect-ratio", "auto 16 /"); +test_invalid_value("aspect-ratio", "16 / auto"); </script> diff --git a/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/replaced-element-033.html b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/replaced-element-033.html new file mode 100644 index 00000000000..f7f01993eee --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-sizing/aspect-ratio/replaced-element-033.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>CSS aspect-ratio: img block size with box-sizing (vertical writing mode)</title> +<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#aspect-ratio"> +<link rel="match" href="../../reference/ref-filled-green-100px-square.xht" /> +<style> +img { + writing-mode: vertical-rl; + border-top: 40px solid green; +} +</style> + +<p>Test passes if there is a filled green square and <strong>no red</strong>.</p> + +<!-- + 1st: A green rect 60x100. + border-top is 60x40 and the content box is 60x60. + We use 'aspect-ratio: auto && <ratio>', so the aspect-ratio works with + content-box dimensions always. The inline size of the content box is + (100px - 40px) = 60px, so the block size is 60px * 1/1 = 60px. + + 2nd: A green rect 20x100. + border-top is 20x40 and the content box is 20x60. + + 3rd: A green rect 20x100. + border-top is 20x40 and the content box is 20x60 because we compute + the block size by aspect-ratio which works with border-box and so the + block size is 100px / 5 = 20px. +--> +<img src="support/1x1-green.png" style="height: 100px; aspect-ratio: auto 1/10; box-sizing: border-box;" +><img src="support/1x1-green.png" style="height: 60px; aspect-ratio: 1/3; box-sizing: content-box;" +><img src="support/1x1-green.png" style="height: 100px; aspect-ratio: 1/5; box-sizing: border-box;"> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-1-ref.html b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-1-ref.html index c52d3aa4cd5..c52d3aa4cd5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-1a.html b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-1a.html index 22193a9fd0e..22193a9fd0e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-1a.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-1a.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-1b.html b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-1b.html index 35174cd8857..35174cd8857 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-1b.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-1b.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1-ref.html b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-table-1-ref.html index 0810c428b56..0810c428b56 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-table-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1a.html b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-table-1a.html index f5abc672a85..f5abc672a85 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1a.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-table-1a.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1b.html b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-table-1b.html index 0395c07af34..0395c07af34 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/block-size-with-min-or-max-content-table-1b.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/block-size-with-min-or-max-content-table-1b.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html index 263a0869aed..263a0869aed 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1.html b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1.html index 68860be2234..68860be2234 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html index 172f00d58ee..172f00d58ee 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html index 8592c7d66bf..8592c7d66bf 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html index f7844a0b34e..f7844a0b34e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/hori-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/support/min-content-max-content.css b/tests/wpt/web-platform-tests/css/css-sizing/support/min-content-max-content.css index 60947153b35..60947153b35 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/support/min-content-max-content.css +++ b/tests/wpt/web-platform-tests/css/css-sizing/support/min-content-max-content.css diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html index 0a5de8da146..0a5de8da146 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1.html b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1.html index 15fdaa9dacc..15fdaa9dacc 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html index 5a170d5c4c3..5a170d5c4c3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html index 8b863db4045..8b863db4045 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html index 55223533a2e..55223533a2e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html +++ b/tests/wpt/web-platform-tests/css/css-sizing/vert-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html diff --git a/tests/wpt/web-platform-tests/css/css-tables/col-definite-max-size-001-ref.html b/tests/wpt/web-platform-tests/css/css-tables/col-definite-max-size-001-ref.html new file mode 100644 index 00000000000..835113b2e61 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/col-definite-max-size-001-ref.html @@ -0,0 +1,131 @@ +<!doctype html> +<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com"> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + <td style="border-color:white; border-style:solid"></td> + <td style="border-color:white; border-style:solid"></td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + <td style="border-color:white; border-style:solid"></td> + <td style="border-color:white; border-style:solid"></td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + <td style="border-color:white; border-style:solid"></td> + <td style="border-color:white; border-style:solid"></td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:40px"> + <col style="width:40px"> + <col style="width:40px"> + <col style="width:40px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/col-definite-max-size-001.html b/tests/wpt/web-platform-tests/css/css-tables/col-definite-max-size-001.html new file mode 100644 index 00000000000..bcd7ca2fd89 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/col-definite-max-size-001.html @@ -0,0 +1,172 @@ +<!doctype html> +<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#missing-cells-fixup"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#total-horizontal-border-spacing"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#outer-max-content"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1692607"> +<link rel="match" href="col-definite-max-size-001-ref.html"> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:0; width:100px"> + <col style="max-width:0; width:100px"> + <col style="max-width:0; width:100px"> + <col style="max-width:0; width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:0; min-width:100px"> + <col style="max-width:0; min-width:100px"> + <col style="max-width:0; min-width:100px"> + <col style="max-width:0; min-width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:10%; width:100px"> + <col style="max-width:10%; width:100px"> + <col style="max-width:10%; width:100px"> + <col style="max-width:10%; width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:calc(100px + 1%); width:100px"> + <col style="max-width:calc(100px + 1%); width:100px"> + <col style="max-width:calc(100px + 1%); width:100px"> + <col style="max-width:calc(100px + 1%); width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:0; width:calc(100px + 1%)"> + <col style="max-width:0; width:calc(100px + 1%)"> + <col style="max-width:0; width:calc(100px + 1%)"> + <col style="max-width:0; width:calc(100px + 1%)"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:0; width:50%"> + <col style="max-width:0; width:50%"> + <col style="max-width:0; width:50%"> + <col style="max-width:0; width:50%"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col> + <col> + <col style="max-width:0"> + <col style="max-width:0"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:40px; width:100px"> + <col style="max-width:40px; width:100px"> + <col style="max-width:40px; width:100px"> + <col style="max-width:40px; width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:40px; width:calc(100px + 1%)"> + <col style="max-width:40px; width:calc(100px + 1%)"> + <col style="max-width:40px; width:calc(100px + 1%)"> + <col style="max-width:40px; width:calc(100px + 1%)"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="max-width:40px; width:50%"> + <col style="max-width:40px; width:50%"> + <col style="max-width:40px; width:50%"> + <col style="max-width:40px; width:50%"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col> + <col> + <col style="max-width:40px"> + <col style="max-width:40px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/col-definite-min-size-001.html b/tests/wpt/web-platform-tests/css/css-tables/col-definite-min-size-001.html new file mode 100644 index 00000000000..7fb6632eec4 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/col-definite-min-size-001.html @@ -0,0 +1,67 @@ +<!doctype html> +<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#missing-cells-fixup"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#total-horizontal-border-spacing"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#outer-max-content"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1692607"> +<link rel="match" href="col-definite-size-001-ref.html"> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="min-width:100px"> + <col style="min-width:100px"> + <col style="min-width:100px"> + <col style="min-width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="min-width:calc(100px + 1%)"> + <col style="min-width:calc(100px + 1%)"> + <col style="min-width:calc(100px + 1%)"> + <col style="min-width:calc(100px + 1%)"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="min-width:50%"> + <col style="min-width:50%"> + <col style="min-width:50%"> + <col style="min-width:50%"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col> + <col> + <col style="min-width:0"> + <col style="min-width:0"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/col-definite-size-001-ref.html b/tests/wpt/web-platform-tests/css/css-tables/col-definite-size-001-ref.html new file mode 100644 index 00000000000..7f666ef0a39 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/col-definite-size-001-ref.html @@ -0,0 +1,45 @@ +<!doctype html> +<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com"> +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + <td style="border-color:white; border-style:solid"></td> + <td style="border-color:white; border-style:solid"></td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/col-definite-size-001.html b/tests/wpt/web-platform-tests/css/css-tables/col-definite-size-001.html new file mode 100644 index 00000000000..7f30417557f --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/col-definite-size-001.html @@ -0,0 +1,67 @@ +<!doctype html> +<link rel="author" title="Mats Palmgren" href="mailto:mats@mozilla.com"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#missing-cells-fixup"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#total-horizontal-border-spacing"> +<link rel="help" href="https://drafts.csswg.org/css-tables-3/#outer-max-content"> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1692607"> +<link rel="match" href="col-definite-size-001-ref.html"> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + <col style="width:100px"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:calc(100px + 1%)"> + <col style="width:calc(100px + 1%)"> + <col style="width:calc(100px + 1%)"> + <col style="width:calc(100px + 1%)"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col style="width:50%"> + <col style="width:50%"> + <col style="width:50%"> + <col style="width:50%"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> + +<table border="1" cellspacing="10"> + <colgroup> + <col> + <col> + <col style="width:0"> + <col style="width:0"> + </colgroup> + <thead> + <tr> + <td>1</td> + <td>2</td> + </tr> + </thead> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/column-track-merging.html b/tests/wpt/web-platform-tests/css/css-tables/column-track-merging.html new file mode 100644 index 00000000000..6dba9e6f607 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/column-track-merging.html @@ -0,0 +1,278 @@ +<!doctype html> +<title>Column track merging</title> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<script src="/resources/check-layout-th.js"></script> +<link rel="author" title="Aleks Totic" href="atotic@chromium.org" /> +<link rel="help" href="https://www.w3.org/TR/css-tables-3/#dimensioning-the-row-column-grid--step2" /> +<style> + +main table { + border: 10px solid gray; + border-spacing: 20px; +} + +main td { + width: 50px; + height:50px; + padding: 0; + background:linear-gradient(to right, yellow, orange); +} +main caption { + background: #EEE; +} +main .desc { + margin-top: 20px; + color: rgb(50,0,0); +} +main pre { + white-space: pre-wrap; + +} +</style> +<h3>Column merging investigation</h3> +<o>Empty columns is a column that has no originating cells</o> +<p><a href="https://www.w3.org/TR/css-tables-3/#dimensioning-the-row-column-grid--step2">Table standard</a> discusses this under "track merging".</p> +<ul> + <li>Do empty columns get coalesced?</li> + <li>How does this interact with table-layout:fixed, table width</li> + <li>Is there a difference between columns defined by COL, vs TD.colspan? Yes!</li> + <li>Do COLs with specified width get merged?</li> +</ul> +<p>Compatibility</p> +<li>Edge17 has a bug where width of a colspanned cell always includes cell width + width of border spacing. It should be max(cell width, border spacing)</li> +<li>Safari matches Chrome Legacy. TD-originated columns are always merged.</li> +<li>Firefox follows the standard, but has a few bugs.</li> +<main> + +<h3>TD merging</h3> + +<pre class="desc">Auto table, and TD.colspan=10 + FF/Chrome Legacy/Safari: Standard. Tracks merge. + Edge17: Tracks do not merge. Wide cell is 180px (9 * border spacing) +</pre> +<table id="td_auto" data-expected-width=180> +<caption>auto</caption> +<tr> + <td colspan=10 data-expected-width=50></td> + <td></td> +</tr> +<tr> + <td colspan=10></td> + <td></td> +</tr> +</table> + +<pre class="desc">Auto table(400px), and TD.colspan=10 + FF/Chrome Legacy/Safari/Edge17: Standard. Tracks merge. Colspan cell grows because it is unconstrained. +</pre> +<table id="td_auto_width" style="width:400px" data-expected-width=400> +<caption>auto 400px</caption> +<tr> + <td colspan=10 data-expected-width=270></td> + <td></td> +</tr> +<tr> + <td colspan=10></td> + <td></td> +</tr> +</table> + +<pre class="desc">Auto table(130px), and TD.colspan=10 + FF/Chrome Legacy/Safari: Standard. Tracks merge. Colspan cell shrinks to min width becuase it is unconstrained. + Edge17: Non-compliant, buggy. Wide cell too wide, narrow cell disappears. +</pre> +<table id="td_auto_width_130" style="width:130px" data-expected-width=130> +<caption>auto 130px</caption> +<tr> + <td colspan=10 data-expected-width=10><div style="width:10px"></div></td> + <td></td> +</tr> +<tr> + <td colspan=10></td> + <td></td> +</tr> +</table> + +<pre class="td_fixed">Fixed(400px) table, and TD.colspan=10 + Chrome/Safari: Non-compliant. Tracks merge. Cells are the same size, fixed algo distributes extra width evenly. + Firefox: Standard. + Edge17: Standard, buggy. Wide cell too wide. Edge's bug is that it computes max width as (width + border_spacing) instead of max(width, border_spacing). +</pre> +<table id="td_fixed" style="table-layout:fixed; width: 400px" data-expected-width=400> +<caption>fixed 400px</caption> +<tr> + <td colspan=10 data-expected-width=180></td> + <td></td> +</tr> +<tr> + <td colspan=10></td> + <td></td> +</tr> +</table> + +<pre class="td_fixed">Fixed(130px) table, and TD.colspan=10 + Chrome/Safari: Non-compliant.Tracks merge, cells same size. + Firefox: Standard + buggy. Table does not grow. + Edge17: Standard + buggy. Wide cell too wide. +</pre> +<table id="td_fixed" style="table-layout:fixed; width: 130px" data-expected-width=310> +<caption>fixed 130px</caption> +<tr> + <td colspan=10 data-expected-width=180></td> + <td></td> +</tr> +<tr> + <td colspan=10></td> + <td></td> +</tr> +</table> + +<h3>COL merging. Same tests with COL span=10 replacing TD</h3> + +<pre class="desc">Auto table + FF/Chrome Legacy/Safari, Edge17: Standard. wide cell is 50px, tracks do merge. +</pre> +<table id="col_auto" data-expected-width=180> +<caption>auto</caption> +<col span=10> +<tr> + <td data-expected-width=50></td> + <td></td> +</tr> +<tr> + <td></td> + <td></td> +</tr> +</table> + +<pre class="desc">Auto table(400px) + FF/Chrome Legacy/Safari, Edge17: Standard. Both cells grow the same because unconstrained. +</pre> +<table id="col_auto_width" style="width:400px" data-expected-width=400> +<caption>auto 400px</caption> +<col span=10> +<tr> + <td data-expected-width=160></td> + <td></td> +</tr> +<tr> + <td ></td> + <td></td> +</tr> +</table> + +<pre class="desc">Auto table(130px) + FF/Chrome Legacy/Safari, Edge17: Standard. Both cells shrink. +</pre> +<table id="col_auto_width_130" style="width:130px" data-expected-width=130> +<caption>auto 130px</caption> +<col span=10> +<tr> + <td data-expected-width=28><div style="width:10px"></div></td> + <td></td> +</tr> +<tr> + <td></td> + <td></td> +</tr> +</table> + +<pre class="desc">Fixed(400px) table + Chrome/Safari,Firefox: Standard. + Edge17: Buggy. Fixed cells grow to fill table. +</pre> +<table id="col_fixed" style="table-layout:fixed; width: 400px" data-expected-width=400> +<caption>fixed 400px</caption> +<col span=10> +<tr> + <td data-expected-width=50></td> + <td></td> +</tr> +<tr> + <td></td> + <td></td> +</tr> +</table> + +<pre class="td_fixed">Fixed(130px) table + Chrome/Safari: Standard, very buggy. Non-collapsed columns shrink to single border spacing. + Firefox: Standard. + Edge17: Non-compliant, collapses columns. +</pre> +<table id="col_fixed_130" style="table-layout:fixed; width: 130px" data-expected-width=340> +<col span=10> +<caption>fixed 130px</caption> +<tr> + <td data-expected-width=50></td> + <td></td> +</tr> +<tr> + <td></td> + <td></td> +</tr> +</table> + +<h3>COL merging when COL has specified width.</h3> + +<ul><li>Chrome Legacy/Edge17/Safari: non-compliant, merge COLs with specified widths. + <li>Firefox: Standard, unless COL width is 0px. Buggy, does not include border-spacing around columns.</ul> +<pre class="desc">Auto table, COL width 30px. + Chrome Legacy/Edge17/Safari: non-compliant, merge. + Firefox: Standard, buggy. does not include border-spacing around columns. +</pre> +<table id="col_auto" data-expected-width=580> +<caption>auto col 30px</caption> +<col span=10 style="width:30px"> +<tr> + <td data-expected-width=50></td> + <td></td> +</tr> +<tr> + <td></td> + <td></td> +</tr> +</table> + +<pre class="desc">Auto table, COL width 5%. + Chrome Legacy/Edge17/Safari: non-compliant, merge. + Firefox: Standard, buggy. does not include border-spacing around columns. +</pre> +<table id="col_auto" data-expected-width=640> +<caption>auto col 10%</caption> +<col span=5 style="width:10%"> +<tr> + <td data-expected-width=100></td> + <td></td> +</tr> +<tr> + <td></td> + <td></td> +</tr> +</table> + +<pre class="desc">Auto table, COL width 0px. + Everyone: merges COL +</pre> +<table id="col_auto" data-expected-width=180> +<caption>auto col 0px</caption> +<col span=10 style="width:0px"> +<tr> + <td data-expected-width=50></td> + <td></td> +</tr> +<tr> + <td></td> + <td></td> +</tr> +</table> + + +</main> +<script> + checkLayout("main table"); +</script> + + +</body> +</html> diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/caption-with-multicol-table-cell.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/caption-with-multicol-table-cell.html new file mode 100644 index 00000000000..2228ad69366 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/caption-with-multicol-table-cell.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1177684"> +<table> + <caption> + <div style="columns:1; display:table-cell;"></div> + </caption> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/dialog-table-crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/dialog-table-crash.html new file mode 100644 index 00000000000..decc3eb1fbe --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/dialog-table-crash.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<link rel="help" href="https://crbug.com/1175425"> + +<div id="container" style="border-collapse:collapse;"> + <div id="insertBefore"></div> + <div style="display:table-row-group;"></div> + <div id="victim" style="border:solid; display:table-row-group;"> + <div style="display:table-cell;"></div> + </div> +</div> + +<script> + document.body.offsetTop; + container.insertBefore(insertBefore, victim) +</script> + diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/empty_cells_crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/empty_cells_crash.html new file mode 100644 index 00000000000..7635afc9e6c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/empty_cells_crash.html @@ -0,0 +1,17 @@ +<!doctype html> +<style> + table { + empty-cells: hide; + border-collapse: collapse; + } + td { + padding:0; + } +</style> + +<table> + <td></td> + <td colspan=2></td> + <td></td> +</table> +</div> diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/expression_width_crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/expression_width_crash.html new file mode 100644 index 00000000000..5c636bef6ab --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/expression_width_crash.html @@ -0,0 +1,5 @@ +<!doctype html> +<table > + <col style="width:min(100px,30%)"> + <td style="width:min(100px,30%)"></td> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/inline-splitting-crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/inline-splitting-crash.html new file mode 100644 index 00000000000..decc3eb1fbe --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/inline-splitting-crash.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<link rel="help" href="https://crbug.com/1175425"> + +<div id="container" style="border-collapse:collapse;"> + <div id="insertBefore"></div> + <div style="display:table-row-group;"></div> + <div id="victim" style="border:solid; display:table-row-group;"> + <div style="display:table-cell;"></div> + </div> +</div> + +<script> + document.body.offsetTop; + container.insertBefore(insertBefore, victim) +</script> + diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/large-border-crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/large-border-crash.html new file mode 100644 index 00000000000..b61ea7f046b --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/large-border-crash.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<link rel="help" href="https://crbug.com/1178044"> +<table border="2026722966"><td> diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/legacy_ng_mix_crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/legacy_ng_mix_crash.html new file mode 100644 index 00000000000..7e7377ef7eb --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/legacy_ng_mix_crash.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<p>PASS if no crash or DCHECK failure.</p> +<table id="elm" style="columns:2;"> + <colgroup style="column-count:2;"> +</table> +<script> + document.body.offsetTop; + elm.style.columns = "auto"; +</script> diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/orthogonal-cell-crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/orthogonal-cell-crash.html new file mode 100644 index 00000000000..0f27d3eccd1 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/orthogonal-cell-crash.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<link rel="help" href="https://crbug.com/1178130"> +<table> + <td style="height: 100%; writing-mode: vertical-rl;"></td> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/crashtests/uninitialized_read_crash.html b/tests/wpt/web-platform-tests/css/css-tables/crashtests/uninitialized_read_crash.html new file mode 100644 index 00000000000..5aa3b223cd5 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/crashtests/uninitialized_read_crash.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<link rel="help" href="https://crbug.com/1172819"> + +<table style="height:69080px;width:100px;background: blue;border-spacing:2px;"> + +<thead style="height:400%"> + <tr style="height:18px"></tr> +</thead> +<tbody> + <tr style="height: 2712px"></tr> +</tbody> +<tbody> + <tr style="height: 1694px"></tr> +</tbody> +<tbody> + <tr style="height: 6436px"></tr> +</tbody> +</table> diff --git a/tests/wpt/web-platform-tests/css/css-tables/tentative/table-width-redistribution-fixed.html b/tests/wpt/web-platform-tests/css/css-tables/tentative/table-width-redistribution-fixed.html new file mode 100644 index 00000000000..ed126710601 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-tables/tentative/table-width-redistribution-fixed.html @@ -0,0 +1,332 @@ +<!doctype html> +<title>Fixed table final assignable distribution</title> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<script src="/resources/check-layout-th.js"></script> +<link rel="stylesheet" type="text/css" href="./support/table-tentative.css"> +<link rel="author" title="Aleks Totic" href="atotic@chromium.org" /> +<link rel="help" href="https://www.w3.org/TR/css-tables-3/#distributing-width-to-columns" /> +<style> + main table { + background: gray; + border-spacing: 8px 8px; + table-layout: fixed; + } + main table:hover { table-layout: auto; } /* useful for comparisons */ + main td { + background: #BFB; + font-size: 10px; + } + main td > div { + display: inline-block; + background: rgba(56,162,56,0.3); + height:10px; + } +</style> +<main> +<h1>Fixed tables: Compute column computed widths from assignable table width</h1> +<ul> + <li>auto columns have a min width of 0. Max width still gets computed.</li> + <li>percent columns have a min width of 0.</li> + <li>fixed column.min_width is css width. It never changes.</li> + <li>fixed column.max_width is max(cells.max_width, css width).</li> + <li>colspan header cells distribute + <ul> + <li>max_width evenly between columns.</li> + <li>do not distribute min width</li> + <li>percentage evenly between columns</li> + </ul> + </li> +</ul> + +<h2>Is table treated as fixed?</h2> +<p class="testdesc">table width:auto is not treated as fixed.</p> +<table style="table-layout:fixed; width:auto" data-expected-width=324> + <tr> + <td style="width:200px">200</td> + <td><div style="width:100px">min</div></td> + </tr> +</table> +<p class="testdesc">table width:px is treated as fixed.</p> +<table style="table-layout:fixed; width:224px" data-expected-width=224> + <tr> + <td style="width:200px">200</td> + <td><div style="width:100px">min</div></td> + </tr> +</table> +<p class="testdesc">table width:min-content is treated as fixed.</p> +<table style="table-layout:fixed; width:min-content" data-expected-width=224> + <tr> + <td style="width:200px">200</td> + <td><div style="width:100px">min</div></td> + </tr> +</table> + +<h2>Fixed only</h2> + +<p class="testdesc">Table: 50px; C0:100/50/100 C1:100/50/75 +When table.css_width is < columns.css_width, how is the conflict resolved? +columns.css_width wins</p> +<table style="width:50px" data-expected-width=224> + <tr> + <td style="width:100px" data-expected-width=100> + <div style="width:50px">50</div><div style="width:50px">50</div></td> + <td style="width:100px" data-expected-width=100> + <div style="width:50px">50</div><div style="width:25px">25</div></td> + </tr> +</table> + +<p class="testdesc">Table: 300px; C0:100/100/200 C1:100/90/115 +When table.css_width is > columns.css_width , how is the conflict resolved? +table.css_width wins</p> +<table style="width:300px" data-expected-width=300> + <tr> + <td style="width:100px" data-expected-width=138> + <div style="width:100px">100</div><div style="width:100px">100</div></td> + <td style="width:100px" data-expected-width=138> + <div style="width:90px">90</div><div style="width:25px">25</div></td> + </tr> +</table> + +<p class="testdesc">Table: 300px; C0:100/50/50 C1:100/100/100 +Fixed cells must grow, but their min widths differ. +Fixed cells grow in proportion to their css width. +<table style="width:calc(300px + 24px)" data-expected-width=324> + <tr> + <td style="width:100px" data-expected-width=150> + <div style="width:50px">50</div></td> + <td style="width:100px" data-expected-width=150> + <div style="width:100px">100</div></td> + </tr> +</table> + +<p class="testdesc">Table: 50px; C0:100/50/50 C1:100/100/100 +What happens when column.min_width > column.css_width +column.css_width wins over column.min_width. +<table style="width:100px" data-expected-width=224> + <tr> + <td style="width:100px" data-expected-width=100> + <div style="width:200px"></div></td> + <td style="width:100px" data-expected-width=100> + <div style="width:200px"></div></td> + </tr> +</table> + +<p class="testdesc">Table: 1px. +What happens to min_width when multiple cells specify css_width of the same column? +1st cell wins. +<table style="width:1px" data-expected-width=116> + <tr> + <td style="width:100px" data-expected-width=100> + <div style="width:200px">200</div></td> + </tr> + <td style="width:150px" data-expected-width=100> + <div style="width:150px">150</div></td> + </tr> +</table> + +<h2>Auto only</h2> + +<p class="testdesc">Width is distributed evenly +</p> +<table style="width:548px"> + <tr> + <td data-expected-width=100><div style="width:10px;height:30px"></div></td> + <td data-expected-width=100><div style="width:20px;height:30px"></div></td> + <td data-expected-width=100><div style="width:30px;height:30px"></div></td> + <td data-expected-width=100><div style="width:40px;height:30px"></div></td> + <td data-expected-width=100><div style="width:120px;height:30px"></div></td> + </tr> +</table> + +<h2>Colspan distribution</h2> + +<p class="testdesc">Table: 1px +Does column.min_width change with colspan distribution from later rows to first row? +No +<table style="width:1px" data-expected-width=74> + <tr> + <td data-expected-width=0> + <div style="width:50px"></div></td> + <td style="width:50px" data-expected-width=50> + <div style="width:50px"></div></td> + </tr> + <tr> + <td colspan=2 style="width:200px" data-expected-width=58> + <div style="width:200px"></div></td> + </tr> +</table> + +<p class="testdesc">Table: 632px +Does column.percent change with colspan distribution? +No. +<table style="width:632px" data-expected-width=632> + <tr> + <td data-expected-width=360> + <div style="width:50px"></div></td> + <td style="width:20%" data-expected-width=120> + <div style="width:50px"></div></td> + <td style="width:20%" data-expected-width=120></td> + </tr> + <tr> + <td colspan="2" style="width:90%"> + <div style="width:100px"></div></td> + <td>auto</td> + </tr> +</table> + +<h2>Colspan header cells</h2> +<section> +<ol> + <li>Fixed/percentage colspan cells get distributed evenly.</li> + <li>Auto cells</li> +</ol> + +<p class="testdesc">Assignable: 400px +Fixed header cells with colspan. +Columns divded evenly</p> +<p class="error">Legacy Chrome is slightly off, something about spacing and wide cells.</p> +<table style="width:calc(600px + 40px)" data-expected-width=640> + <tr> + <td colspan=2 style="width:108px" data-expected-width=208>108</td> + <td colspan=2 style="width:208px" data-expected-width=408>208</td> + </tr> + <tr> + <td data-expected-width=100>1</td> + <td>1</td> + <td data-expected-width=200>1</td> + <td>1</td> + </tr> +</table> + +<p class="testdesc">Assignable: 400px, C0:40% C1:20% C2:40% +Percentage header cells with colspan +C0 splits into C0.0 and C0.1, 16px each with 20% +C1 splits into C1.0 and C1.1, 6px each with 10% +Assignable width is 400, everyone gets according to percent. +80/80/40/40/160.</p> +<p class="error">Firefox is slightly off, with C2 taking 6px more. Unknown what math is used to get this answer.</p> +<table style="width:448px" data-expected-width=448> + <tr> + <td colspan=2 style="width:40%" data-expected-width=168><div style="width:40px"></div></td> + <td colspan=2 style="width:20%" data-expected-width=88><div style="width:160px"></div></td> + <td style="width:40%" data-expected-width=160><div style="width:40px"></div></td> + </tr> + <tr> + <td data-expected-width=80>Auto</td> + <td data-expected-width=80>Auto</td> + <td data-expected-width=40>Auto</td> + <td data-expected-width=40>Auto</td> + <td data-expected-width=160>Auto</td> + </tr> +</table> + +<p class="testdesc">Assignable: 1px, C0 Auto/100 colspan=2 , C1 100/Auto +Auto header cells with colspan, table is min width +min_width does not get redistributed. +</p> +<table style="width:1px" data-expected-width=132> + <tr> + <td colspan=2 data-expected-width=8> + <div style="width:100px">100</div></td> + <td style="width:100px" data-expected-width=100>100</td> + </tr> + <tr> + <td data-expected-width=0>x</td> + <td data-expected-width=0>x</td> + <td data-expected-width=100>x</td> + </tr> +</table> + +<p class="testdesc">Assignable: 200; C0: colspan:2 Auto C1:colspan 8 Auto +Auto colspan cells, and nothing else. Tricky because this means that internally +table has to represent 8 cells, and wide cells that span beyond table width +are usually truncated. +C0: 20*2+8=48, C1: 20*8 + 7*8=216</p> +<table style="width:calc(200px + 88px)" data-expected-width=288> + <tr> + <td colspan=2 style="height:20px" data-expected-width=48></td> + <td colspan=8 style="height:20px" data-expected-width=216></td> + </tr> +</table> + +<h2>Percentage only</h2> + +<p class="testdesc">Assignable: 100px;columns add to 100%, auto width +Columns are exact percentage size. +<table style="width:calc(100px + 32px)" data-expected-width=132> + <tr> + <td style="width:50%" data-expected-width=50>50%</td> + <td style="width:30%" data-expected-width=30>30%</td> + <td style="width:20%" data-expected-width=20>20%</td> + </tr> +</table> + +<p class="testdesc">Assignable: 100px;columns add to 50%, auto width +Columns grow proportional to percent. +<table style="width:calc(100px + 32px)" data-expected-width=132> + <tr> + <td style="width:25%" data-expected-width=50>25%</td> + <td style="width:15%" data-expected-width=30>15%</td> + <td style="width:10%" data-expected-width=20>10%</td> + </tr> +</table> + + +<p class="testdesc">Assignable: 100px;columns add to 50%, with min width +Min width is ignored. +<table style="width:calc(100px + 32px)" data-expected-width=132> + <tr> + <td style="width:50%" data-expected-width=50><div style="width:50px">50</div></td> + <td style="width:30%" data-expected-width=30><div style="width:50px">50</div></td> + <td style="width:20%" data-expected-width=20><div style="width:50px">50</div></td> + </tr> +</table> + +<p class="testdesc">Assignable: 100px;columns add to 1000% +Columns are scaled so they add up to 100% +<table style="width:calc(100px + 32px)" data-expected-width=132> + <tr> + <td style="width:200%" data-expected-width=20><div style="width:50px">50</div></td> + <td style="width:300%" data-expected-width=30><div style="width:50px">50</div></td> + <td style="width:500%" data-expected-width=50><div style="width:50px">50</div></td> + </tr> +</table> + + + +<h2>Percentage/auto/fixed mix</h2> + +<p class="testdesc">Assignable: 100px;C0:50% C1:100px C2: Auto +C0: 50% becomes +<table style="width:calc(100px + 32px)" data-expected-width=132> + <tr> + <td style="width:50%" data-expected-width=50>50%</td> + <td style="width:30px" data-expected-width=30>30px</td> + <td data-expected-width=20></td> + </tr> +</table> + +<p class="testdesc">Assignable: 100px;C0:50% C1:50px +Clean split +<table style="width:calc(100px + 24px)" data-expected-width=124> + <tr> + <td style="width:50%" data-expected-width=50>50%</td> + <td style="width:50px" data-expected-width=50>50px</td> + </tr> +</table> + +<p class="testdesc">Assignable: 100px;C0:20% C1:60% C2:60px +Overconstrained: widths add up to 140. +Fixed widths get distributed first, percentage takes the rest. +<table style="width:calc(100px + 32px)" data-expected-width=132> + <tr> + <td style="width:20%" data-expected-width=10>20%</td> + <td style="width:60%" data-expected-width=30>60%</td> + <td style="width:60px" data-expected-width=60>60px</td> + </tr> +</table> +</main> +<script> + checkLayout("table"); +</script> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-002.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-002.html new file mode 100644 index 00000000000..490f78e94cf --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-002.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Text: 'hyphens: auto' with a valid 'lang' attribute specification</title> +<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> +<link rel="help" title="5.4. Hyphenation: the hyphens property" href="https://drafts.csswg.org/css-text-3/#hyphenation"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-auto"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap"> +<link rel="match" href="reference/hyphens-auto-002-ref.html"> +<meta name="flags" content="ahem"> +<meta name="assert" content="Although hyphenation is enabled, honoring 'auto' value for the 'hyphens' CSS property and a valid 'lang' attribute, the words fit. However, we should break preserved white spaces, honoring 'white-space: pre-wrap' to prevent overflow."> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +<style> +.fail { + position: absolute; + background: red; + width: 100px; + height: 100px; + z-index: -1; +} +.test { + font: 50px/1 Ahem; + width: 6ch; + color: green; + + white-space: pre-wrap; + hyphens: auto; +} +</style> +<body lang="en"> + <p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p> + <div class="fail"></div> + <div class="test">XX XX</div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-003.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-003.html new file mode 100644 index 00000000000..347805bac74 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-003.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Text: 'hyphens: auto' with a valid 'lang' attribute specification</title> +<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> +<link rel="help" title="5.4. Hyphenation: the hyphens property" href="https://drafts.csswg.org/css-text-3/#hyphenation"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-auto"> +<link rel="match" href="reference/hyphens-auto-002-ref.html"> +<meta name="flags" content="ahem"> +<meta name="assert" content="Although hyphenation is enabled, honoring 'auto' value for the 'hyphens' CSS property and a valid 'lang' attribute, the words fit. However, we should break after the preserved combination of white+ideographic space sequence, honoring 'white-space: normal' to prevent overflow."> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +<style> +.fail { + position: absolute; + background: red; + width: 100px; + height: 100px; + z-index: -1; +} +.test { + font: 50px/1 Ahem; + width: 4ch; + color: green; + + hyphens: auto; +} +</style> +<body lang="en"> + <p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p> + <div class="fail"></div> + <div class="test">XX        XX</div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-004.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-004.html new file mode 100644 index 00000000000..3d99985a00c --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-004.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Text: 'hyphens: auto' with a valid 'lang' attribute specification</title> +<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> +<link rel="help" title="5.4. Hyphenation: the hyphens property" href="https://drafts.csswg.org/css-text-3/#hyphenation"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-auto"> +<link rel="match" href="reference/hyphens-auto-004M-ref.html"> +<link rel="match" href="reference/hyphens-auto-004H-ref.html"> +<meta name="flags" content="ahem"> +<meta name="assert" content="When 'hyphens' is set to 'auto' and when 'lang' attribute is also set to a valid value, then words may be broken at hyphenation opportunities determined automatically by an hyphenation resource appropriate to the language of the text involved."> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +<style> +.test { + border: solid 1px; + font: 32px/1 monospace; + width: 6ch; + + hyphens: auto; +} +</style> +<body lang="en"> + <div class="test">regulation        implementation now</div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-005.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-005.html new file mode 100644 index 00000000000..c7176d1dfc8 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/hyphens-auto-005.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Text: 'hyphens: auto' with a valid 'lang' attribute specification</title> +<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> +<link rel="help" title="5.4. Hyphenation: the hyphens property" href="https://drafts.csswg.org/css-text-3/#hyphenation"> +<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-hyphens-auto"> +<link rel="match" href="reference/hyphens-auto-005M-ref.html"> +<link rel="match" href="reference/hyphens-auto-005H-ref.html"> +<meta name="flags" content="ahem"> +<meta name="assert" content="When 'hyphens' is set to 'auto' and when 'lang' attribute is also set to a valid value, then words may be broken at hyphenation opportunities determined automatically by an hyphenation resource appropriate to the language of the text involved."> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +<style> +.test { + border: solid 1px; + font: 50px/1 monospace; + width: 4ch; + + hyphens: auto; +} +</style> +<body lang="en"> + <div class="test">regulation</div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-002-ref.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-002-ref.html new file mode 100644 index 00000000000..dece5f73944 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-002-ref.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>CSS Text Reference File</title> +<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> +<style> +div { + width: 100px; + height: 100px; + background: green; +} +</style> +<body> + <p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p> + <div></div> +</body> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-004H-ref.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-004H-ref.html new file mode 100644 index 00000000000..cb26d85e8d9 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-004H-ref.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reference Test</title> + + <link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> + + <style> + div + { + border: solid 1px; + font: 32px/1 monospace; + width: 6ch; + } + </style> + + <body lang="en"> + + <div>regu-<br>lation<br><br>imple-<br>menta-<br>tion<br>now</div> + +<!-- + + Hyphen-minus == - == - + + Hyphen == ‐ == ‐ + +--> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-004M-ref.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-004M-ref.html new file mode 100644 index 00000000000..4e234bf76de --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-004M-ref.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reference Test</title> + + <link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> + + <style> + div + { + border: solid 1px; + font: 32px/1 monospace; + width: 6ch; + } + </style> + + <body lang="en"> + + <div>regu‐<br>lation<br><br>imple‐<br>menta‐<br>tion<br>now</div> + +<!-- + + Hyphen-minus == - == - + + Hyphen == ‐ == ‐ + +--> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-005H-ref.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-005H-ref.html new file mode 100644 index 00000000000..6bd86d36aa2 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-005H-ref.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reference Test</title> + + <link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> + + <style> + div + { + border: solid 1px; + font: 50px/1 monospace; + width: 4ch; + } + </style> + + <body> + + <div>reg-<br>ula-<br>tion</div> + +<!-- + + Hyphen-minus == - == - + + Hyphen == ‐ == ‐ + +--> diff --git a/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-005M-ref.html b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-005M-ref.html new file mode 100644 index 00000000000..38b00cef8bf --- /dev/null +++ b/tests/wpt/web-platform-tests/css/css-text/hyphens/reference/hyphens-auto-005M-ref.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> + + <meta charset="UTF-8"> + + <title>CSS Reference Test</title> + + <link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" /> + + <style> + div + { + border: solid 1px; + font: 50px/1 monospace; + width: 4ch; + } + </style> + + <body> + + <div>reg‐<br>ula‐<br>tion</div> + +<!-- + + Hyphen-minus == - == - + + Hyphen == ‐ == ‐ + +--> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-image-gradient-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-image-gradient-1-ref.html index 3fefef23ff7..3fefef23ff7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-image-gradient-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-image-gradient-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-image-gradient-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-image-gradient-1.html index b2c02897425..b2c02897425 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-image-gradient-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-image-gradient-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-linear-gradient-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-linear-gradient-1-ref.html index 5c8a604122a..5c8a604122a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-linear-gradient-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-linear-gradient-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-linear-gradient-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-linear-gradient-1.html index 6d60e8dd9f0..6d60e8dd9f0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-linear-gradient-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-linear-gradient-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-position-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-position-1-ref.html index b64aa46bf2e..b64aa46bf2e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-position-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-position-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-position-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-position-1.html index 68351aef6e5..68351aef6e5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-position-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-position-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-size-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-size-1-ref.html index 5385c46ef52..5385c46ef52 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-size-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-size-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-size-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-background-size-1.html index f52ceeeabe1..f52ceeeabe1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-background-size-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-background-size-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-border-radius-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-border-radius-1-ref.html index 24681d08a43..24681d08a43 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-border-radius-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-border-radius-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-border-radius-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-border-radius-1.html index ad36d36492a..ad36d36492a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-border-radius-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-border-radius-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-block-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-height-block-1-ref.html index be75d5d6a3d..be75d5d6a3d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-block-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-height-block-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-height-block-1.html index 9525c87de56..9525c87de56 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-height-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-table-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-height-table-1-ref.html index d936f69ed03..d936f69ed03 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-table-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-height-table-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-table-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-height-table-1.html index 64b7b5dc31c..64b7b5dc31c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-height-table-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-height-table-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-margin-block-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-margin-block-1-ref.html index acfc0889206..acfc0889206 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-margin-block-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-margin-block-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-margin-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-margin-block-1.html index 7372bd2aed4..7372bd2aed4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-margin-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-margin-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-height-block-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-max-height-block-1-ref.html index 9907c9984b2..9907c9984b2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-height-block-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-max-height-block-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-height-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-max-height-block-1.html index d0f978a2b40..d0f978a2b40 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-height-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-max-height-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-width-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-max-width-block-1.html index c6a31d2293d..c6a31d2293d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-width-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-max-width-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-width-block-intrinsic-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-max-width-block-intrinsic-1-ref.html index 8cb693160cc..8cb693160cc 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-width-block-intrinsic-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-max-width-block-intrinsic-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-width-block-intrinsic-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-max-width-block-intrinsic-1.html index 433c1950cd3..433c1950cd3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-max-width-block-intrinsic-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-max-width-block-intrinsic-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-height-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-min-height-block-1.html index ed60d9a03ad..ed60d9a03ad 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-height-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-min-height-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-width-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-min-width-block-1.html index 8195f68591c..8195f68591c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-width-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-min-width-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-width-block-intrinsic-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-min-width-block-intrinsic-1-ref.html index 82fae9717fe..82fae9717fe 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-width-block-intrinsic-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-min-width-block-intrinsic-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-width-block-intrinsic-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-min-width-block-intrinsic-1.html index edc564331ea..edc564331ea 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-min-width-block-intrinsic-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-min-width-block-intrinsic-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-bottom-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-bottom-1.html index b3bf1ccb7e2..b3bf1ccb7e2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-bottom-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-bottom-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-left-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-left-1.html index 8ee19c7a569..8ee19c7a569 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-left-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-left-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-right-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-right-1.html index 3fcdba055e8..3fcdba055e8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-right-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-right-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-top-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-top-1-ref.html index 5c6c06364e9..5c6c06364e9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-top-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-top-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-top-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-top-1.html index 7ac2dfe2a44..7ac2dfe2a44 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-absolute-top-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-absolute-top-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-bottom-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-bottom-1.html index 1f121ac541d..1f121ac541d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-bottom-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-bottom-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-left-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-left-1-ref.html index 82ab51df06b..82ab51df06b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-left-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-left-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-left-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-left-1.html index b091ca5502c..b091ca5502c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-left-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-left-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-right-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-right-1.html index 28e154777cb..28e154777cb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-right-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-right-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-top-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-top-1-ref.html index db6785b2922..db6785b2922 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-top-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-top-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-top-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-top-1.html index f2e9e4d9b6c..f2e9e4d9b6c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-offsets-relative-top-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-offsets-relative-top-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-padding-block-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-padding-block-1-ref.html index ca472b38ec7..ca472b38ec7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-padding-block-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-padding-block-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-padding-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-padding-block-1.html index 97dbaeb2f66..97dbaeb2f66 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-padding-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-padding-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-1-ref.html index 4631c002170..4631c002170 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-1.html index dec795b2dc1..dec795b2dc1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-intrinsic-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-intrinsic-1-ref.html index fa221c015d1..fa221c015d1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-intrinsic-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-intrinsic-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-intrinsic-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-intrinsic-1.html index b60a7e949a6..b60a7e949a6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-text-indent-intrinsic-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-text-indent-intrinsic-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-transform-origin-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-transform-origin-1-ref.html index d27da3efea1..d27da3efea1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-transform-origin-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-transform-origin-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-transform-origin-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-transform-origin-1.html index bbe70749db6..bbe70749db6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-transform-origin-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-transform-origin-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-vertical-align-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-vertical-align-1-ref.html index 8e6d42425d0..8e6d42425d0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-vertical-align-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-vertical-align-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-vertical-align-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-vertical-align-1.html index f612b962e95..f612b962e95 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-vertical-align-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-vertical-align-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-1-ref.html index d62da1ad32e..d62da1ad32e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-1.html index d261d03984c..d261d03984c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-intrinsic-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-intrinsic-1-ref.html index e381bbab519..e381bbab519 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-intrinsic-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-intrinsic-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-intrinsic-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-intrinsic-1.html index 0c80b960d1d..0c80b960d1d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-block-intrinsic-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-block-intrinsic-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-auto-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-auto-1-ref.html index d161ecc6f31..d161ecc6f31 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-auto-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-auto-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-auto-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-auto-1.html index c3d147c0b1c..c3d147c0b1c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-auto-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-auto-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-fixed-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-fixed-1-ref.html index 9ae2d347c6b..9ae2d347c6b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-fixed-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-fixed-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-fixed-1.html b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-fixed-1.html index 808a61274d3..808a61274d3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/calc-width-table-fixed-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/calc-width-table-fixed-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/rem-root-font-size-restyle-1-ref.html b/tests/wpt/web-platform-tests/css/css-values/rem-root-font-size-restyle-1-ref.html index a1a1430d35e..a1a1430d35e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/rem-root-font-size-restyle-1-ref.html +++ b/tests/wpt/web-platform-tests/css/css-values/rem-root-font-size-restyle-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/rem-root-font-size-restyle-1.html b/tests/wpt/web-platform-tests/css/css-values/rem-root-font-size-restyle-1.html index 0f236cb3df9..0f236cb3df9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/rem-root-font-size-restyle-1.html +++ b/tests/wpt/web-platform-tests/css/css-values/rem-root-font-size-restyle-1.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/support/blue-32x32.png b/tests/wpt/web-platform-tests/css/css-values/support/blue-32x32.png Binary files differindex deefd19b2ac..deefd19b2ac 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/support/blue-32x32.png +++ b/tests/wpt/web-platform-tests/css/css-values/support/blue-32x32.png diff --git a/tests/wpt/web-platform-tests/css/css-values/urls/empty.html b/tests/wpt/web-platform-tests/css/css-values/urls/empty.html index 3ab7079396c..a3de129840f 100644 --- a/tests/wpt/web-platform-tests/css/css-values/urls/empty.html +++ b/tests/wpt/web-platform-tests/css/css-values/urls/empty.html @@ -26,15 +26,11 @@ const ids = [ "external-quoted" ]; -const inline_url = location.href; -const external_url = new URL(document.querySelector("link[rel=stylesheet]").href, location.href).href; - for (let id of ids) { test(function() { const el = document.getElementById(id); - const expected = id.startsWith("inline-") ? inline_url : external_url; const style = window.getComputedStyle(el); - assert_equals(style["background-image"], 'url("' + expected + '")'); + assert_equals(style["background-image"], 'url("about:invalid")'); }, "empty URL: " + id); } </script> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/color-green-ref.html b/tests/wpt/web-platform-tests/css/css-variables/support/color-green-ref.html index 0eabe58c81f..0eabe58c81f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/color-green-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/support/color-green-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-declaration.css b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-declaration.css index 9ba1b9d3288..9ba1b9d3288 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-declaration.css +++ b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-declaration.css diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-font-face.css b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-font-face.css index e3ad1eddb42..e3ad1eddb42 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-font-face.css +++ b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-font-face.css diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-reference.css b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-reference.css index 0c697fdcf91..0c697fdcf91 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-reference.css +++ b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-reference.css diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-supports.css b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-supports.css index 96582bfd8f1..96582bfd8f1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/support/external-variable-supports.css +++ b/tests/wpt/web-platform-tests/css/css-variables/support/external-variable-supports.css diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-01.html index 8c3ff421cf3..8c3ff421cf3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-02.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-02.html index 4ee52b58600..4ee52b58600 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-02.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-02.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-03.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-03.html index 50aa2f35825..50aa2f35825 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-03.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-03.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-04.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-04.html index 52d5d314897..52d5d314897 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-04.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-04.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-05.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-05.html index 0403bcaec18..0403bcaec18 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-05.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-05.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-06.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-06.html index bccb52da555..bccb52da555 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-06.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-06.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-07.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-07.html index ecd74b2d406..ecd74b2d406 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-07.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-07.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-08.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-08.html index e3cfd9c089c..e3cfd9c089c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-08.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-08.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-09.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-09.html index 898b973bfa2..898b973bfa2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-09.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-09.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-10.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-10.html index cfcfd32eacb..cfcfd32eacb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-10.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-10.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-11.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-11.html index ed011ad15b3..ed011ad15b3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-11.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-11.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-12.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-12.html index 3815754afd8..3815754afd8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-12.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-12.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-13.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-13.html index 5c262b813f6..5c262b813f6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-13.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-13.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-14.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-14.html index 153cfb9948e..153cfb9948e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-14.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-14.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-15-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-15-ref.html index 6679d0de955..6679d0de955 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-15-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-15-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-15.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-15.html index 9b0fcffc731..9b0fcffc731 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-15.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-15.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-16-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-16-ref.html index 6679d0de955..6679d0de955 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-16-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-16-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-16.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-16.html index bb047bb53d6..bb047bb53d6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-16.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-16.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-17-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-17-ref.html index b0075fc38f7..b0075fc38f7 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-17-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-17-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-17.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-17.html index f0e3c9c79b3..f0e3c9c79b3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-17.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-17.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-18-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-18-ref.html index 6679d0de955..6679d0de955 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-18-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-18-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-18.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-18.html index 7262e375e61..7262e375e61 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-18.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-18.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-19.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-19.html index da0a8252377..da0a8252377 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-19.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-19.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-20.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-20.html index 9bffae2d502..9bffae2d502 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-20.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-20.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-21.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-21.html index 0a91196df00..0a91196df00 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-21.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-21.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-22.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-22.html index a96d9cf1893..a96d9cf1893 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-22.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-22.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-23.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-23.html index b3b1d18234b..b3b1d18234b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-23.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-23.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-24.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-24.html index ead972c2aad..ead972c2aad 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-24.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-24.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-25.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-25.html index 64d19973d74..64d19973d74 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-25.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-25.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-26.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-26.html index 79ff10d2b64..79ff10d2b64 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-26.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-26.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-28.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-28.html index f6f78911bd5..f6f78911bd5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-28.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-28.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-29.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-29.html index a8847f5ed9c..a8847f5ed9c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-29.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-29.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-30.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-30.html index e354abb2d4b..e354abb2d4b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-30.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-30.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-31.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-31.html index 4a24bf2bd5d..4a24bf2bd5d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-31.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-31.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-32.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-32.html index d11478e8f5f..d11478e8f5f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-32.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-32.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-33.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-33.html index 521db857f54..521db857f54 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-33.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-33.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-34.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-34.html index c6b4d42d348..c6b4d42d348 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-34.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-34.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-35.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-35.html index f1289069f62..f1289069f62 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-35.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-35.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-36.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-36.html index 1f984fe7a62..1f984fe7a62 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-36.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-36.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-37.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-37.html index bd4b1c0f4b4..bd4b1c0f4b4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-37.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-37.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-38.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-38.html index ece8cf8ffa4..ece8cf8ffa4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-38.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-38.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-39.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-39.html index d1caabbd6d9..d1caabbd6d9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-39.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-39.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-40.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-40.html index 62a4e21700f..62a4e21700f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-40.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-40.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-41.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-41.html index c1c585b0f96..c1c585b0f96 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-41.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-41.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-42.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-42.html index 1a60ba2393f..1a60ba2393f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-42.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-42.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-43.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-43.html index 73aaef2b89c..73aaef2b89c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-43.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-43.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-44.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-44.html index f2a968d250d..f2a968d250d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-44.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-44.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-45.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-45.html index a5003d9a690..a5003d9a690 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-45.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-45.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-46.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-46.html index c846b1cf979..c846b1cf979 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-46.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-46.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-47.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-47.html index 2f06d093f7a..2f06d093f7a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-47.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-47.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-48.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-48.html index 9abf328977f..9abf328977f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-48.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-48.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-49.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-49.html index 9a4b984b806..9a4b984b806 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-49.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-49.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-50.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-50.html index 0545b003d98..0545b003d98 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-50.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-50.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-51.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-51.html index eac02079bfa..eac02079bfa 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-51.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-51.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-52.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-52.html index e913e2dcd92..e913e2dcd92 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-52.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-52.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-53.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-53.html index b8b6a8cf5ca..b8b6a8cf5ca 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-53.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-53.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-54.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-54.html index 8e0b39c4c4c..8e0b39c4c4c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-54.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-54.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-55.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-55.html index 2fb68d516db..2fb68d516db 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-55.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-55.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-56.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-56.html index 21378f892d6..21378f892d6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-56.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-56.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-57.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-57.html index aa8d84c6ace..aa8d84c6ace 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-57.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-57.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-58.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-58.html index b05f9678315..b05f9678315 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-58.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-58.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-59.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-59.html index 2032a342d61..2032a342d61 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-59.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-59.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-60.html b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-60.html index 3304eb4a38b..3304eb4a38b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-declaration-60.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-declaration-60.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-declaration-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-external-declaration-01.html index bae5abf700a..bae5abf700a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-declaration-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-external-declaration-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-font-face-01-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-external-font-face-01-ref.html index 3e7fd9a2636..3e7fd9a2636 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-font-face-01-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-external-font-face-01-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-font-face-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-external-font-face-01.html index 74132d59c65..74132d59c65 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-font-face-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-external-font-face-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-reference-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-external-reference-01.html index ee40db762cd..ee40db762cd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-reference-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-external-reference-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-supports-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-external-supports-01.html index dd3ad172818..dd3ad172818 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-external-supports-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-external-supports-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-01-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-01-ref.html index 3e7fd9a2636..3e7fd9a2636 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-01-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-01-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-01.html index 168d970075d..168d970075d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-02-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-02-ref.html index 3e7fd9a2636..3e7fd9a2636 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-02-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-02-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-02.html b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-02.html index 8b29d8d63a0..8b29d8d63a0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-font-face-02.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-font-face-02.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-01.html index 1737b2e6552..1737b2e6552 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-02.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-02.html index 92e4aedca6b..92e4aedca6b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-02.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-02.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-03.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-03.html index bd50b9ab85f..bd50b9ab85f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-03.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-03.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-04.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-04.html index 060e55c9531..060e55c9531 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-04.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-04.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-05.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-05.html index 129e19f1d74..129e19f1d74 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-05.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-05.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-06.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-06.html index 464ce37ed99..464ce37ed99 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-06.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-06.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-07.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-07.html index d557161edcf..d557161edcf 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-07.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-07.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-08.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-08.html index d7aa58df6f9..d7aa58df6f9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-08.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-08.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-09.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-09.html index f2128cf2cb1..f2128cf2cb1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-09.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-09.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-10.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-10.html index 73251154c92..73251154c92 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-10.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-10.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-11.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-11.html index 81dec5a76c2..81dec5a76c2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-11.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-11.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-12-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-12-ref.html index 8fcd177cb90..8fcd177cb90 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-12-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-12-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-12.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-12.html index 968b71ebb90..968b71ebb90 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-12.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-12.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-13.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-13.html index b7249981f3c..b7249981f3c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-13.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-13.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-14.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-14.html index 7196f5879dc..7196f5879dc 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-14.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-14.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-15.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-15.html index 01cd29b2b28..01cd29b2b28 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-15.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-15.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-16.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-16.html index f27108ce925..f27108ce925 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-16.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-16.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-17.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-17.html index 2f677a6a1c1..2f677a6a1c1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-17.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-17.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-18.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-18.html index a858b2b6bcd..a858b2b6bcd 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-18.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-18.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-19.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-19.html index c3b0413e23a..c3b0413e23a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-19.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-19.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-20.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-20.html index 868a576949e..868a576949e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-20.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-20.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-21.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-21.html index f1ad08bda22..f1ad08bda22 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-21.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-21.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-22.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-22.html index a02f49ac079..a02f49ac079 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-22.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-22.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-23.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-23.html index 44afd5a6ba2..44afd5a6ba2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-23.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-23.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-24.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-24.html index 25c9c6743a5..25c9c6743a5 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-24.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-24.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-25.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-25.html index 4ed514c9b83..4ed514c9b83 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-25.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-25.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-26.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-26.html index ca3b01b26db..ca3b01b26db 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-26.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-26.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-27.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-27.html index efd8418c84a..efd8418c84a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-27.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-27.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-28.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-28.html index 7796f1b8800..7796f1b8800 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-28.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-28.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-29.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-29.html index b01873fc367..b01873fc367 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-29.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-29.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-30.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-30.html index 9082c3058e1..9082c3058e1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-30.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-30.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-31.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-31.html index bfe4677bd12..bfe4677bd12 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-31.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-31.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-32.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-32.html index e1f5789c3a0..e1f5789c3a0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-32.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-32.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-33.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-33.html index c527bcdd42f..c527bcdd42f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-33.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-33.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-34.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-34.html index 8cb14db28b9..8cb14db28b9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-34.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-34.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-35.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-35.html index cddc46691f0..cddc46691f0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-35.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-35.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-36-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-36-ref.html index 3af79f7948e..3af79f7948e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-36-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-36-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-36.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-36.html index e4f4d502606..e4f4d502606 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-36.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-36.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-37-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-37-ref.html index 3af79f7948e..3af79f7948e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-37-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-37-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-37.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-37.html index 391c5532c33..391c5532c33 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-37.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-37.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-38-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-38-ref.html index 8b4e262c785..8b4e262c785 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-38-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-38-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-38.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-38.html index 8c37c10301b..8c37c10301b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-38.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-38.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-39.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-39.html index 21b7fd820ff..21b7fd820ff 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-39.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-39.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-40-ref.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-40-ref.html index a612d33f093..a612d33f093 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-40-ref.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-40-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-40.html b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-40.html index 07697c77b75..07697c77b75 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-reference-40.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-reference-40.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-01.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-01.html index 1b7872619c6..1b7872619c6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-01.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-01.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-02.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-02.html index 2b74a468fc2..2b74a468fc2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-02.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-02.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-03.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-03.html index 8d19666d91e..8d19666d91e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-03.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-03.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-04.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-04.html index f8b0458eff9..f8b0458eff9 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-04.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-04.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-05.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-05.html index 08a7e74967e..08a7e74967e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-05.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-05.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-06.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-06.html index d83f60055c3..d83f60055c3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-06.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-06.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-07.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-07.html index d3bcc22ee2b..d3bcc22ee2b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-07.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-07.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-08.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-08.html index 637bfb8de7a..637bfb8de7a 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-08.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-08.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-09.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-09.html index f82e8602961..f82e8602961 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-09.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-09.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-10.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-10.html index c8d77476b75..c8d77476b75 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-10.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-10.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-11.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-11.html index 835d06b52d6..835d06b52d6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-11.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-11.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-12.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-12.html index 594e78e21d0..594e78e21d0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-12.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-12.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-13.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-13.html index d6e8a680051..d6e8a680051 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-13.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-13.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-14.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-14.html index 831ee1b0570..831ee1b0570 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-14.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-14.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-15.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-15.html index 205c86d97b4..205c86d97b4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-15.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-15.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-16.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-16.html index 102d2c1cdfc..102d2c1cdfc 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-16.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-16.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-17.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-17.html index 88f7d4c408d..88f7d4c408d 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-17.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-17.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-18.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-18.html index 81423b4d1ac..81423b4d1ac 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-18.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-18.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-19.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-19.html index df903995668..df903995668 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-19.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-19.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-20.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-20.html index 2673b380255..2673b380255 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-20.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-20.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-21.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-21.html index 67010c2edb1..67010c2edb1 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-21.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-21.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-22.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-22.html index 38d1edc558f..38d1edc558f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-22.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-22.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-23.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-23.html index 6ab942132af..6ab942132af 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-23.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-23.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-24.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-24.html index 18488cdab01..18488cdab01 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-24.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-24.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-25.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-25.html index 7ba0aafb713..7ba0aafb713 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-25.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-25.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-26.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-26.html index b49df519647..b49df519647 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-26.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-26.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-27.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-27.html index be67b4d06eb..be67b4d06eb 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-27.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-27.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-28.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-28.html index 2ccd2b35739..2ccd2b35739 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-28.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-28.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-29.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-29.html index 193cb55c73b..193cb55c73b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-29.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-29.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-30.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-30.html index 937b7613e61..937b7613e61 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-30.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-30.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-31.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-31.html index d6dec7b8438..d6dec7b8438 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-31.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-31.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-32.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-32.html index 1cc391d14ed..1cc391d14ed 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-32.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-32.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-33.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-33.html index f26eec2356c..f26eec2356c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-33.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-33.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-34.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-34.html index aac50bf8be2..aac50bf8be2 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-34.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-34.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-35.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-35.html index 5966803871e..5966803871e 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-35.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-35.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-36.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-36.html index 14d5bc9e88f..14d5bc9e88f 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-36.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-36.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-37.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-37.html index 828fb2f7f5b..828fb2f7f5b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-37.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-37.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-38.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-38.html index 97e019ea83b..97e019ea83b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-38.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-38.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-39.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-39.html index d67ac48f601..d67ac48f601 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-39.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-39.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-40.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-40.html index 91fd83c0785..91fd83c0785 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-40.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-40.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-41.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-41.html index aff08cd2bf6..aff08cd2bf6 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-41.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-41.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-42.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-42.html index ae90e57d7c8..ae90e57d7c8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-42.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-42.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-43.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-43.html index b5f176d0642..b5f176d0642 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-43.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-43.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-44.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-44.html index 4c18960f300..4c18960f300 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-44.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-44.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-45.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-45.html index 24b1eecf828..24b1eecf828 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-45.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-45.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-46.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-46.html index c9d3f5b2ad3..c9d3f5b2ad3 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-46.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-46.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-47.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-47.html index 946963fe4e4..946963fe4e4 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-47.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-47.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-48.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-48.html index 7ab0c84556c..7ab0c84556c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-48.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-48.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-49.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-49.html index aa500b90828..aa500b90828 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-49.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-49.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-50.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-50.html index c2185c9571c..c2185c9571c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-50.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-50.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-51.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-51.html index 0aa6d4397f8..0aa6d4397f8 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-51.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-51.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-52.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-52.html index 9cc7d214896..9cc7d214896 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-52.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-52.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-53.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-53.html index a40c2ffa636..a40c2ffa636 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-53.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-53.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-54.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-54.html index b6009e1f768..b6009e1f768 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-54.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-54.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-55.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-55.html index f4b92a2f674..f4b92a2f674 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-55.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-55.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-56.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-56.html index 07b188f76f0..07b188f76f0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-56.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-56.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-57.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-57.html index 5cb8b336439..5cb8b336439 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-57.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-57.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-58.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-58.html index 5eb41b4328b..5eb41b4328b 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-58.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-58.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-59.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-59.html index 16669e66c27..16669e66c27 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-59.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-59.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-60.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-60.html index 438d23f84ca..438d23f84ca 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-60.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-60.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-61.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-61.html index 62f9683d4ab..62f9683d4ab 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-61.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-61.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-62.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-62.html index 39c55b21696..39c55b21696 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-62.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-62.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-63.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-63.html index 74235832c78..74235832c78 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-63.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-63.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-64.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-64.html index 2b3eadf24f0..2b3eadf24f0 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-64.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-64.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-65.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-65.html index 4bc9c4e4d1c..4bc9c4e4d1c 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-65.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-65.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-66.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-66.html index a38b044ad15..a38b044ad15 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-66.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-66.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-67.html b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-67.html index 9425e2179ff..9425e2179ff 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/variable-supports-67.html +++ b/tests/wpt/web-platform-tests/css/css-variables/variable-supports-67.html diff --git a/tests/wpt/web-platform-tests/css/cssom-view/add-background-attachment-fixed-during-smooth-scroll-ref.html b/tests/wpt/web-platform-tests/css/cssom-view/add-background-attachment-fixed-during-smooth-scroll-ref.html new file mode 100644 index 00000000000..8bcbf411026 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/cssom-view/add-background-attachment-fixed-during-smooth-scroll-ref.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Add background-attachment:fixed during smooth scroll</title> +<style> +#container { + width: 200px; + height: 200px; + overflow: scroll; + background: linear-gradient(green, blue); + background-attachment: fixed; +} +#content { + width: 7500px; + height: 7500px; +} +</style> +<div id="container"> + <div id="content">Content</div> +</div> +<script> +container.scrollTop = 6000; +</script> diff --git a/tests/wpt/web-platform-tests/css/cssom-view/add-background-attachment-fixed-during-smooth-scroll.html b/tests/wpt/web-platform-tests/css/cssom-view/add-background-attachment-fixed-during-smooth-scroll.html new file mode 100644 index 00000000000..61d8cd7bcb1 --- /dev/null +++ b/tests/wpt/web-platform-tests/css/cssom-view/add-background-attachment-fixed-during-smooth-scroll.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<title>Add background-attachment:fixed during smooth scroll</title> +<link rel="help" href="https://drafts.csswg.org/cssom-view/#dictdef-scrolltooptions"> +<link rel="match" href="add-background-attachment-fixed-during-smooth-scroll-ref.html"> +<script src="/common/reftest-wait.js"></script> +<style> +#container { + width: 200px; + height: 200px; + overflow: scroll; + background: linear-gradient(green, blue); + will-change: transform; +} +#content { + width: 7500px; + height: 7500px; +} +</style> +<script> +function startSmoothScroll() { + var scrollToOptions = {behavior: "smooth", top: 6000}; + container.scrollTo(scrollToOptions); + requestAnimationFrame(preventCompositedScrolling); +} + +function preventCompositedScrolling() { + container.style.backgroundAttachment = "fixed"; + requestAnimationFrame(waitForSmoothScrollEnd); +} + +function waitForSmoothScrollEnd() { + if (container.scrollTop == 6000) { + takeScreenshot(); + } else { + window.requestAnimationFrame(waitForSmoothScrollEnd); + } +} + +onload = () => { + requestAnimationFrame(startSmoothScroll); +} +</script> +<div id="container"> + <div id="content">Content</div> +</div> diff --git a/tests/wpt/web-platform-tests/css/cssom-view/background-change-during-smooth-scroll.html b/tests/wpt/web-platform-tests/css/cssom-view/background-change-during-smooth-scroll.html new file mode 100644 index 00000000000..b5c28c77f8e --- /dev/null +++ b/tests/wpt/web-platform-tests/css/cssom-view/background-change-during-smooth-scroll.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>Background change from opaque to transparent during smooth scroll</title> +<link rel=help href="https://drafts.csswg.org/cssom-view/#dictdef-scrolltooptions"> +<style> +#container { + width: 200px; + height: 200px; + overflow: scroll; + background: white; +} +#content { + width: 7500px; + height: 7500px; +} +</style> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +var t = async_test('background change during smooth scroll'); + +function startSmoothScroll() { + var scrollToOptions = {behavior: "smooth", top: 6000}; + container.scrollTo(scrollToOptions); + requestAnimationFrame(preventCompositedScrolling); +} + +function preventCompositedScrolling() { + container.style.background = "transparent"; + requestAnimationFrame(waitForSmoothScrollEnd); +} + +function waitForSmoothScrollEnd() { + if (container.scrollTop == 6000) { + t.done(); + } else { + window.requestAnimationFrame(waitForSmoothScrollEnd); + } +} + +onload = () => { + requestAnimationFrame(startSmoothScroll); +} +</script> +<div id="container"> + <div id="content">Content</div> +</div> diff --git a/tests/wpt/web-platform-tests/css/selectors/focus-visible-002.html b/tests/wpt/web-platform-tests/css/selectors/focus-visible-002.html index 441c1dae988..5fd9f9cafdf 100644 --- a/tests/wpt/web-platform-tests/css/selectors/focus-visible-002.html +++ b/tests/wpt/web-platform-tests/css/selectors/focus-visible-002.html @@ -79,18 +79,19 @@ <div> <textarea class="check" id="input14">Focus me.</textarea> </div> - <div> - <select class="check" id="input15"> - <option>Focus me.</option> - <option>Focus me.</option> - </select> - </div> <script> - for (const target of document.querySelectorAll(".check")) { + setup({ explicit_done: true }); + + const elements = document.querySelectorAll(".check"); + for (let i = 0; i < elements.length; i++) { + const target = elements[i]; promise_test(() => { return new Promise(resolve => { target.addEventListener("focus", resolve); - test_driver.click(target); + test_driver.click(target).then(() => { + if (i == (elements.length - 1)) + done(); + }); }).then(() => { assert_equals(getComputedStyle(target).outlineColor, "rgb(0, 128, 0)", `outlineColor for ${target.tagName}#${target.id} should be green`); assert_not_equals(getComputedStyle(target).backgroundColor, "rgb(255, 0, 0)", `backgroundColor for ${target.tagName}#${target.id} should NOT be red`); diff --git a/tests/wpt/web-platform-tests/css/selectors/focus-visible-006.html b/tests/wpt/web-platform-tests/css/selectors/focus-visible-006.html index a5256c107a7..c203c06f6ea 100644 --- a/tests/wpt/web-platform-tests/css/selectors/focus-visible-006.html +++ b/tests/wpt/web-platform-tests/css/selectors/focus-visible-006.html @@ -45,16 +45,15 @@ <span id="el" contenteditable>Focus me</span> </div> <script> - var actions_promise; + setup({ explicit_done: true }); + async_test(function(t) { - el.addEventListener("focus", t.step_func(function() { + el.addEventListener("focus", t.step_func_done(function() { assert_equals(getComputedStyle(el).outlineColor, "rgb(0, 128, 0)", `outlineColor for ${el.tagName}#${el.id} should be green`); assert_not_equals(getComputedStyle(el).backgroundColor, "rgb(255, 0, 0)", `backgroundColor for ${el.tagName}#${el.id} should NOT be red`); - // Make sure the test finishes after all the input actions are completed. - actions_promise.then( () => t.done() ); })); - actions_promise = test_driver.click(el); + test_driver.click(el).then(done()); }, "Focus should always match :focus-visible on content editable divs"); </script> </body> diff --git a/tests/wpt/web-platform-tests/css/selectors/focus-visible-007.html b/tests/wpt/web-platform-tests/css/selectors/focus-visible-007.html index 0fc196cbecd..149a6f68fe8 100644 --- a/tests/wpt/web-platform-tests/css/selectors/focus-visible-007.html +++ b/tests/wpt/web-platform-tests/css/selectors/focus-visible-007.html @@ -36,7 +36,7 @@ <li>If the user-agent does not claim to support the <code>:focus-visible</code> pseudo-class then SKIP this test.</li> <li>Use the mouse to focus the element below that says "Click me."</li> <li>If the element has a red outline, then the test result is FAILURE.</li> - <li>Press the SHIFT key.</li> + <li>Press the ENTER key.</li> <li>If the element now has a green outline and not red background, then the test result is SUCCESS.</li> </ol> @@ -65,7 +65,8 @@ one.addEventListener("keyup", t.step_func(test_modality_change)); one.removeEventListener("focus", handle_initial_focus); - test_driver.send_keys(one, "\uE050"); + const enter = "\uE007"; + test_driver.send_keys(one, enter); }); const test_modality_change = t.step_func(() => { diff --git a/tests/wpt/web-platform-tests/css/selectors/focus-visible-011.html b/tests/wpt/web-platform-tests/css/selectors/focus-visible-011.html index b0daf34ba8d..cf186d28b83 100644 --- a/tests/wpt/web-platform-tests/css/selectors/focus-visible-011.html +++ b/tests/wpt/web-platform-tests/css/selectors/focus-visible-011.html @@ -21,11 +21,11 @@ border: 0; } - #next:focus-visible { + :focus-visible { outline: green solid 5px; } - #next:focus:not(:focus-visible) { + :focus:not(:focus-visible) { background-color: red; outline: 0; } @@ -37,30 +37,33 @@ <ul id="instructions"> <li>Click "Click here and press right arrow.".</li> <li>Press the right arrow key.</li> - <li>If "Focus moves here." has a red background, then the test result is FAILURE. + <li>If the element has a red background, then the test result is FAILURE. If it has a green outline, then the test result is SUCCESS.</li> </ul> <br /> - <button id="start" tabindex="0">Click here and press right arrow.</button> - <button id="next" tabindex="-1">Focus moves here.</button> + <div id="target" tabindex="0">Click here and press right arrow.</div> <script> - start.addEventListener('keydown', (e) => { + target.addEventListener("keydown", (e) => { + e.preventDefault(); + }); + target.addEventListener("keyup", (e) => { + e.preventDefault(); + }); + target.addEventListener("keypress", (e) => { e.preventDefault(); - next.focus(); }); - async_test(function(t) { - next.addEventListener("focus", t.step_func(() => { - assert_equals(getComputedStyle(next).outlineColor, "rgb(0, 128, 0)", `outlineColor for ${next.tagName}#${next.id} should be green`); - assert_not_equals(getComputedStyle(next).backgroundColor, "rgb(255, 0, 0)", `backgroundColor for ${next.tagName}#${next.id} should NOT be red`); - t.done() - })); + target.addEventListener("focus", () => { + const arrow_right = "\ue014"; + test_driver.send_keys(target, arrow_right); + }); - // \ue014 -> ARROW_RIGHT - test_driver.send_keys(start, "\ue014").catch(t.step_func(() => { - assert_true(false, "send_keys not implemented yet"); - t.done(); + target.addEventListener("keyup", t.step_func_done((e) => { + assert_equals(getComputedStyle(target).outlineColor, "rgb(0, 128, 0)", `outlineColor for ${target.tagName}#${target.id} should be green`); + assert_not_equals(getComputedStyle(target).backgroundColor, "rgb(255, 0, 0)", `backgroundColor for ${target.tagName}#${target.id} should NOT be red`); })); + + test_driver.click(target); }, ":focus-visible matches even if preventDefault() is called"); </script> </body> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reftest.list deleted file mode 100644 index 21cdb382112..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/reftest.list +++ /dev/null @@ -1,42 +0,0 @@ -# background-repeat round/space test cases -# background-repeat space is broken under SWGL (see bug 1658815) -== background-repeat-space-1a.html background-repeat-space-1-ref.html -== background-repeat-space-1b.html background-repeat-space-1-ref.html -== background-repeat-space-1c.html background-repeat-space-1-ref.html -== background-repeat-space-2.html background-repeat-space-2-ref.html -== background-repeat-space-3.html background-repeat-space-3-ref.html -== background-repeat-space-4.html background-repeat-space-4-ref.html -== background-repeat-space-5.html background-repeat-space-5-ref.html -== background-repeat-space-6.html background-repeat-space-6-ref.html -== background-repeat-space-7.html background-repeat-space-7-ref.html -== background-repeat-space-8.html background-repeat-space-8-ref.html -== background-repeat-space-9.html background-repeat-space-9-ref.html -== background-repeat-space-10.html background-repeat-space-10-ref.html -== background-repeat-round-1a.html background-repeat-round-1-ref.html -== background-repeat-round-1b.html background-repeat-round-1-ref.html -== background-repeat-round-1c.html background-repeat-round-1-ref.html -== background-repeat-round-1d.html background-repeat-round-1-ref.html -== background-repeat-round-1e.html background-repeat-round-1-ref.html -== background-repeat-round-2.html background-repeat-round-2-ref.html -== background-repeat-round-3.html background-repeat-round-3-ref.html -== background-repeat-round-4.html background-repeat-round-4-ref.html - -#border-image test cases -== border-image-repeat-round-1.html border-image-repeat-round-1-ref.html -== border-image-repeat-round-2.html border-image-repeat-round-2-ref.html -== border-image-repeat-space-1.html border-image-repeat-space-1-ref.html -== border-image-repeat-space-2.html border-image-repeat-space-2-ref.html -== border-image-repeat-space-3.html border-image-repeat-space-3-ref.html -== border-image-repeat-space-4.html border-image-repeat-space-4-ref-1.html -== border-image-repeat-space-4-ref-1.html border-image-repeat-space-4-ref-2.html -== border-image-repeat-space-5.html border-image-repeat-space-5-ref-1.html -== border-image-repeat-space-5-ref-1.html border-image-repeat-space-5-ref-2.html -== border-image-repeat-space-6.html border-image-repeat-space-6-ref.html -== border-image-repeat-space-7.html border-image-repeat-space-7-ref.html -== border-image-repeat-1.html border-image-repeat-1-ref.html - -# background-attachment test cases -== background-attachment-fixed-inside-transform-1.html background-attachment-fixed-inside-transform-1-ref.html - -# box-shadow with currentcolor test cases -== box-shadow-currentcolor.html box-shadow-currentcolor-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/border.png b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/border.png Binary files differdeleted file mode 100644 index 7a657391d65..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/background/support/border.png +++ /dev/null diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/break3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/break3/reftest.list deleted file mode 100644 index f174e8e3e0b..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/break3/reftest.list +++ /dev/null @@ -1 +0,0 @@ -== moz-block-fragmentation-001.html moz-block-fragmentation-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/reftest.list deleted file mode 100644 index 34f8d2570d8..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/color4/reftest.list +++ /dev/null @@ -1,10 +0,0 @@ -#css-color-4 function -#hsl -== background-color-hsl-001.html background-color-hsl-001-ref.html -== background-color-hsl-002.html background-color-hsl-002-ref.html -== background-color-hsl-003.html background-color-hsl-003-ref.html -== background-color-hsl-004.html background-color-hsl-004-ref.html -#rgb -== background-color-rgb-001.html background-color-rgb-001-ref.html -== background-color-rgb-002.html background-color-rgb-002-ref.html -== background-color-rgb-003.html background-color-rgb-003-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-float-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-float-001.html deleted file mode 100644 index fe491aa5502..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-float-001.html +++ /dev/null @@ -1,38 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: layout' should contain floats as a formatting context.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-layout"> - <link rel="match" href="contain-paint-formatting-context-float-001-ref.html"> - <style> - #left { - float: left; - height: 50px; - width: 10px; - background: blue; - } - #a { - contain: layout; - background: red; - margin: 10px; - width: 50px; - height: 50px; - } - #b { - clear: left; - width: 50px; - height: 50px; - background: green; - } - </style> -</head> -<body> - <div id="left"></div> - <div id="a"> - <div id="b"></div> - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-margin-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-margin-001.html deleted file mode 100644 index a8520ee2d1c..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-layout-formatting-context-margin-001.html +++ /dev/null @@ -1,37 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: layout' with a vertical margin child. Margin collapse should not occur, and neither should overflow clipping.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-layout"> - <link rel="match" href="contain-layout-formatting-context-margin-001-ref.html"> - <style> - #a { - contain:layout; - background: blue; - margin: 10px; - width: 50px; - height: 50px; - } - #b { - width: 50px; - height: 40px; - background: green; - margin-top: 10px; - } - #c { - background: lightblue; - width: 50px; - height: 10px; - } - </style> -</head> -<body> - <div id="a"> - <div id="b"></div> - <div id="c"></div> - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-001.html deleted file mode 100644 index 0f7874b71e0..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-001.html +++ /dev/null @@ -1,63 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' with various overflowing block descendants.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-clip-001-ref.html"> - <style> - .root { - contain: paint; - width: 100px; - height: 100px; - background: blue; - margin: 25px; - padding: 25px; - } - .a { - width: 100px; - height: 200px; - background: red; - } - .b { - width: 150px; - height: 150px; - background: green; - position: relative; - top: -25px; - left: -25px; - } - .background { - position: absolute; - top: 0; - left: 0; - width: 200px; - height: 200px; - background: red; - z-index: -1; - } - .foreground { - position: absolute; - top: -25px; - left: -25px; - width: 150px; - height: 150px; - border: 25px solid red; - z-index: 1; - } - </style> -</head> -<body> - <div class="root"> - <div class="a"> - <div class="b"></div> - <!--These two absolutely positioned elements are checking that all sides are--> - <!--clipped. They also test that clipping is done correctly on absolutely--> - <!--positioned elements.--> - <div class="background"></div> - <div class="foreground"></div> - </div> - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-002.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-002.html deleted file mode 100644 index b6fbc9c2927..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-002.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' with overflowing text contents inside a rounded rectangle box.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-clip-002-ref.html"> - <style> - .root { - contain: paint; - width: 100px; - height: 100px; - background: green; - margin: 25px; - padding: 10px; - border-radius: 4em; - } - </style> -</head> -<body> - <div class="root"> - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should - be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing - elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed - nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. - Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. - Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora - torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales - ligula in libero. - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-003.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-003.html deleted file mode 100644 index ed270e9e0cc..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-003.html +++ /dev/null @@ -1,33 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' with overflowing text contents, and 'overflow-y: scroll'.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-clip-003-ref.html"> - <style> - .root { - contain: paint; - overflow-y: scroll; - width: 100px; - height: 100px; - background: green; - margin: 25px; - padding: 25px; - } - </style> -</head> -<body> - <div class="root"> - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should - be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing - elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed - nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. - Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. - Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora - torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales - ligula in libero. - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-004.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-004.html deleted file mode 100644 index 844a59ad691..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-004.html +++ /dev/null @@ -1,33 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' with overflowing text contents, and 'overflow-x: scroll'.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-clip-004-ref.html"> - <style> - .root { - contain: paint; - overflow-x: scroll; - width: 100px; - height: 100px; - background: green; - margin: 25px; - padding: 25px; - } - </style> -</head> -<body> - <div class="root"> - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should - be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing - elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed - nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. - Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. - Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora - torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales - ligula in libero. - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-005.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-005.html deleted file mode 100644 index b2766e374e4..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-005.html +++ /dev/null @@ -1,40 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' on li with overflowing text contents and - bullet, and 'overflow-y: scroll'.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-clip-003-ref.html"> - <style> - ul { - padding: 0; - margin: 0; - } - .root { - contain: paint; - overflow-y: scroll; - width: 100px; - height: 100px; - background: green; - margin: 25px; - padding: 25px; - } - </style> -</head> -<body> - <ul> - <li class="root"> - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should - be clipped to the box. Lorem ipsum dolor sit amet, consectetur adipiscing - elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. - Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis - ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris - massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu - ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur - sodales ligula in libero. - </li> - </ul> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-006.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-006.html deleted file mode 100644 index 14d23f43134..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-clip-006.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' with overflowing text contents while "overflow-clip-box: content-box" enabled.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-clip-006-ref.html"> - <style> - .root { - contain: paint; - width: 100px; - height: 100px; - background: green; - margin: 25px; - padding: 25px; - overflow-clip-box: content-box; - } - </style> -</head> -<body> - <div class="root"> - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA This text should - be clipped to the content box. Lorem ipsum dolor sit amet, consectetur adipiscing - elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed - nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. - Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. - Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora - torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales - ligula in libero. - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-absolute-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-absolute-001.html deleted file mode 100644 index ef564069eb1..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-absolute-001.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' element should contain absolute position elements.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-containing-block-absolute-001-ref.html"> - <style> - #a { - contain: paint; - width: 100px; - height: 100px; - background: red; - margin: 50px; - } - #b { - position: absolute; - top: 0; - left: 0; - width: 100px; - height: 100px; - background: green; - } - </style> -</head> -<body> - <div id="a"> - <div> - <div id="b"></div> - </div> - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-fixed-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-fixed-001.html deleted file mode 100644 index fc5ede156e9..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-containing-block-fixed-001.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' element should contain fixed position elements.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-containing-block-fixed-001-ref.html"> - <style> - #a { - contain: paint; - width: 100px; - height: 100px; - background: red; - margin: 50px; - } - #b { - position: fixed; - top: 0; - left: 0; - width: 100px; - height: 100px; - background: green; - } - </style> -</head> -<body> - <div id="a"> - <div> - <div id="b"></div> - </div> - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-float-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-float-001.html deleted file mode 100644 index e5827209d71..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-float-001.html +++ /dev/null @@ -1,37 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' should contain floats as a formatting context.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-formatting-context-float-001-ref.html"> - <style> - #left { - float: left; - height: 50px; - width: 10px; - background: blue; - } - #a { - contain: paint; - background: red; - margin: 10px; - width: 50px; - height: 50px; - } - #b { - clear: left; - width: 50px; - height: 50px; - background: green; - } - </style> -</head> -<body> - <div id="left"></div> - <div id="a"> - <div id="b"></div> - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-margin-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-margin-001.html deleted file mode 100644 index d3746422865..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-formatting-context-margin-001.html +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: paint' with a vertical margin child. Margin collapse should not occur.</title> - <link rel="author" title="Kyle Zentner" href="mailto:zentner.kyle@gmail.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-formatting-context-margin-001-ref.html"> - <style> - #a { - contain: paint; - background: blue; - margin: 10px; - width: 50px; - height: 50px; - } - #b { - width: 50px; - height: 40px; - background: green; - margin-top: 10px; - } - #c { - background: red; - width: 50px; - height: 10px; - } - </style> -</head> -<body> - <div id="a"> - <div id="b"></div> - <div id="c"></div> - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001a.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001a.html deleted file mode 100644 index 283b8b941a4..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001a.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta charset=utf-8> - <title>CSS-contain test: paint containment on internal table elements except table-cell.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html"> - <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tr element, and confirms contain:paint does not create a stacking context."> - <style> - tr { - contain: paint; - z-index: 10; - } - th { - background-color: blue; - padding-left: 50px; - } - caption { - position: fixed; - background-color: yellow; - z-index: 2; - } - </style> - </head> - <body> - <table> - <caption>PASS</caption> - <tr> - <th> </th> - </tr> - </table> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001b.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001b.html deleted file mode 100644 index cff28b8b5c2..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-internal-table-001b.html +++ /dev/null @@ -1,36 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <meta charset=utf-8> - <title>CSS-contain test: paint containment on internal table elements except table-cell.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html"> - <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tbody element, and confirms contain:paint does not create a stacking context."> - <style> - tbody { - contain: paint; - z-index: 10; - } - th { - background-color: blue; - padding-left: 50px; - } - caption { - position: fixed; - background-color: yellow; - z-index: 2; - } - </style> - </head> - <body> - <table> - <caption>PASS</caption> - <tbody> - <tr> - <th> </th> - </tr> - </tbody> - </table> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-containing-block-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-containing-block-001.html deleted file mode 100644 index 71764e18558..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-containing-block-001.html +++ /dev/null @@ -1,41 +0,0 @@ -<!doctype html> -<html lang=en> - <head> - <meta charset=utf-8> - <title>CSS-contain test: paint containment on internal ruby elements.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-ignored-cases-ruby-containing-block-001-ref.html"> - <meta name="assert" content="Paint containment should not apply to ruby base, ruby base container, ruby text, and ruby text container. This test confirms contain:paint does not act as a containing block for fixed positioned descendants."> - <style> - rb, - rbc, - rt, - rtc { - contain: paint; - background-color: yellow; - font-size: 2em; - } - rbc { - display: ruby-base-container; - } - .contained { - width: 50px; - height: 10px; - background-color: blue; - top: 0; - left: 0; - position: fixed; - } - .wrapper { - display: inline-block; - } - </style> - </head> - <body> - <div class="wrapper"><ruby><rt> <div class="contained"></div></rt></ruby></div> - <div class="wrapper"><ruby><rtc> <div class="contained"></div></rtc></ruby></div> - <div class="wrapper"><ruby><rb> <div class="contained"></div></rb></ruby></div> - <div class="wrapper"><ruby><rbc> <div class="contained"></div></rbc></ruby></div> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html deleted file mode 100644 index 1f1c147f94f..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html +++ /dev/null @@ -1,60 +0,0 @@ -<!doctype html> -<html lang=en> - <head> - <meta charset=utf-8> - <title>CSS-contain test: paint containment on internal ruby elements.</title> - <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com"> - <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint"> - <link rel="match" href="contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html"> - <meta name="assert" content="Paint containment should not apply to ruby base, ruby base container, ruby text, and ruby text container. This test confirms that contain:paint does not create a stacking context and does not apply overflow clipping."> - <style> - div { - position: relative; - } - rb, - rbc, - rt, - rtc { - contain: paint; - } - rbc { - display: ruby-base-container; - } - .contained { - z-index: 5; - width: 70px; - height: 10px; - background-color: blue; - margin-left: -25px; - } - .background { - background-color: yellow; - height: 50px; - width: 50px; - position: fixed; - z-index: 2; - } - .group { - display: inline-block; - } - </style> - </head> - <body> - <div class="group"> - <div class="background"></div> - <ruby><rb> <div class="contained"></div></rb></ruby> - </div> - <div class="group"> - <div class="background"></div> - <ruby><rbc> <div class="contained"></div></rbc></ruby> - </div> - <div class="group"> - <div class="background"></div> - <ruby><rt> <div class="contained"></div></rt></ruby> - </div> - <div class="group"> - <div class="background"></div> - <ruby><rtc> <div class="contained"></div></rtc></ruby> - </div> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001.html deleted file mode 100644 index b29ef0f3848..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001.html +++ /dev/null @@ -1,108 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: size' on buttons should cause them to be sized as if they had no contents.</title> - <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> - <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> - <link rel="match" href="contain-size-button-001-ref.html"> - <style> - button { - contain: size; - border: 1em solid green; - /* In case the testcase's 'inner' text is taller than the button, don't let - it influence its line-box's size. This lets us more-easily compare - sizing between empty buttons vs. contained nonempty buttons. */ - vertical-align: top; - } - .vaBaseline { - vertical-align: baseline; - } - .innerContents { - color: transparent; - height: 100px; - width: 100px; - } - .minWidth { - min-width: 50px; - } - .width { - width: 50px; - } - .minHeight { - min-height: 50px; - background: lightblue; - } - .height { - height: 50px; - background: lightblue; - } - .floatLBasic { - float: left; - } - .floatLWidth { - float: left; - width: 50px; - } - br { clear: both } - .iFlexBasic { - display: inline-flex; - } - .iFlexWidth { - display: inline-flex; - width: 50px; - } - .orthog { - writing-mode: vertical-lr; - } - </style> -</head> -<body> - <!--CSS Test: A size-contained button with no specified size should render at 0 height regardless of content.--> - <button><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained floated button with no specified size should render at 0px by 0px regardless of content.--> - <button class="floatLBasic"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained floated button with specified width and no specified height should render at given width and 0 height regardless of content.--> - <button class="floatLWidth"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained inline-flex button with no specified size should render at 0px by 0px regardless of content.--> - <button class="iFlexBasic"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained inline-flex button with specified width and no specified height should render at given width and 0 height regardless of content.--> - <button class="iFlexWidth"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained button should perform baseline alignment regularly.--> - outside before<button class="vaBaseline"><div class="innerContents">inner</div></button>outside after - <br> - - <!--CSS Test: A size-contained button with specified min-width should render at given min-width and zero height regardless of content.--> - <button class="minWidth"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained button with specified width should render at given width and zero height regardless of content.--> - <button class="width"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained button with specified min-height should render at given min-height regardless of content.--> - <button class="minHeight"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained button with specified height should render at given height regardless of content.--> - <button class="height"><div class="innerContents">inner</div></button> - <br> - - <!--CSS Test: A size-contained button with vertical text should perform baseline alignment regularly.--> - s<button class="orthog vaBaseline"><div class="innerContents">inner</div></button>endtext - <br> - - <!--CSS Test: A size-contained button with inner text should layout the text in the same manner as a container of the same type with identical contents.--> - <button class="height width">inside</button> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-001.html deleted file mode 100644 index 8cea7f5a60e..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-001.html +++ /dev/null @@ -1,99 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: size' on fieldset elements should cause them to be sized as if they had no contents.</title> - <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> - <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> - <link rel="match" href="contain-size-fieldset-001-ref.html"> - <style> - .contain { - contain: size; - visibility: hidden; - border: none; - color: transparent; - } - .container { - border: 10px solid green; - display: inline-block; - vertical-align: top; - } - .innerContents { - height: 50px; - width: 50px; - } - .minHeight { - min-height: 30px; - } - .height { - height: 30px; - } - .minWidth { - min-width: 30px; - } - .width { - width: 30px; - } - </style> -</head> -<body> - <!--Note: The following .container class is used to help test if size-contained - fieldsets and non-contained fieldsets have the same size. Normally, we'd test - that a fieldset with children and size-containment is drawn identically to a - fieldset without containment or children. However, when we have a legend as - a child, border placement and padding of the fieldset are changed. - To check the dimensions between the ref-case and test-case without - failing because of the border/padding differences, we make the fieldset - {visibility:hidden; border:none;} and add a .container wrapper div.--> - - <!--CSS Test: A size-contained fieldset element with no specified size should size itself as if it had no contents.--> - <div class="container"> - <fieldset class="contain"> - <legend>legend</legend> - <div class="innerContents">inner</div> - </fieldset> - </div> - <br> - - <!--CSS Test: A size-contained fieldset element with specified min-height should size itself as if it had no contents.--> - <div class="container"> - <fieldset class="contain minHeight"> - <legend>legend</legend> - <div class="innerContents">inner</div> - </fieldset> - </div> - <br> - - <!--CSS Test: A size-contained fieldset element with specified height should size itself as if it had no contents.--> - <div class="container"> - <fieldset class="contain height"> - <legend>legend</legend> - <div class="innerContents">inner</div> - </fieldset> - </div> - <br> - - <!--CSS Test: A size-contained fieldset element with specified min-width should size itself as if it had no contents.--> - <div class="container"> - <fieldset class="contain minWidth"> - <legend>legend</legend> - <div class="innerContents">inner</div> - </fieldset> - </div> - <br> - - <!--CSS Test: A size-contained fieldset element with specified width should size itself as if it had no contents.--> - <div class="container"> - <fieldset class="contain width"> - <legend>legend</legend> - <div class="innerContents">inner</div> - </fieldset> - </div> - <br> - - <!--CSS Test: A size contained fieldset element with a legend should draw its legend and border in the same way as a non-contained fieldset element--> - <fieldset class="height" style="contain:size;"> - <legend>legend</legend> - </fieldset> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-002.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-002.html deleted file mode 100644 index 67aa64bb271..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-002.html +++ /dev/null @@ -1,35 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: size' on fieldset elements shouldn't prevent them from being baseline-aligned regularly.</title> - <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> - <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> - <link rel="match" href="contain-size-fieldset-002-ref.html"> - <style> - .contain { - contain: size; - border: none; - color: transparent; - visibility: hidden; - } - .innerContents { - height: 50px; - width: 50px; - } - .flexBaselineCheck { - display: flex; - align-items: baseline; - } - </style> -</head> -<body> - <!--CSS Test: A size-contained fieldset element should perform baseline alignment regularly.--> - <div class="flexBaselineCheck"> - outside before<fieldset class="contain"> - <legend>legend</legend> - <div class="innerContents">inner</div> - </fieldset>outside after - </div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-grid-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-grid-001.html deleted file mode 100644 index c81fa1fa1db..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-grid-001.html +++ /dev/null @@ -1,72 +0,0 @@ -<!DOCTYPE HTML> -<html> -<head> - <meta charset="utf-8"> - <title>CSS Test: 'contain: size' on grid elements should cause them to be sized as if they had no contents.</title> - <link rel="author" title="Gerald Squelart" href="mailto:gsquelart@mozilla.com"> - <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> - <link rel="match" href="contain-size-grid-001-ref.html"> - <style> - .contain { - display: grid; - contain:size; - border: 1em solid green; - background: red; - } - .innerContents { - color: transparent; - height: 100px; - width: 100px; - } - .minHeight { - min-height: 40px; - background: lightblue; - } - .height { - height: 40px; - background: lightblue; - } - .maxWidth { - max-width: 40px; - } - .width { - width: 40px; - } - .floatLBasic { - float: left; - } - .floatLWidth { - float: left; - width: 40px; - } - </style> -</head> -<body> - <!--CSS Test: A size-contained grid element with no specified size should render at 0 height regardless of content.--> - <div class="contain"><div class="innerContents">inner</div></div> - <br> - - <!--CSS Test: A size-contained grid element with specified min-height should render at given min-height regardless of content.--> - <div class="contain minHeight"><div class="innerContents">inner</div></div> - <br> - - <!--CSS Test: A size-contained grid element with specified height should render at given height regardless of content.--> - <div class="contain height"><div class="innerContents">inner</div></div> - <br> - - <!--CSS Test: A size-contained grid element with specified max-width should render at given max-width and zero height regardless of content.--> - <div class="contain maxWidth"><div class="innerContents">inner</div></div> - <br> - - <!--CSS Test: A size-contained grid element with specified width should render at given width and zero height regardless of content.--> - <div class="contain width"><div class="innerContents">inner</div></div> - <br> - - <!--CSS Test: A size-contained floated grid element with no specified size should render at 0px by 0px regardless of content.--> - <div class="contain floatLBasic"><div class="innerContents">inner</div></div> - <br> - - <!--CSS Test: A size-contained floated grid element with specified width and no specified height should render at given width and 0 height regardless of content.--> - <div class="contain floatLWidth"><div class="innerContents">inner</div></div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-001.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-001.html deleted file mode 100644 index 4a1cee5c270..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-multicol-001.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<meta charset="utf-8"> - <title>CSS Test: 'contain: size' should force elements to be monolithic, i.e. to not fragment inside a multicol element.</title> - <link rel="author" title="Morgan Rae Reschenberg" href="mailto:mreschenberg@berkeley.edu"> - <link rel="help" href="https://drafts.csswg.org/css-contain/#containment-size"> - <link rel="match" href="contain-size-multicol-001-ref.html"> - <style> - .contain { - contain:size; - } - .cols { - column-count: 3; - column-rule: 1px dotted blue; - column-fill: auto; - border: 2px solid blue; - height: 50px; - width: 300px; - } - .innerObject { - height: 200px; - width: 100px; - background: orange; - } - </style> -</head> - <body> - <div class="cols"> - <div class="contain innerObject"> - </div> - </div> - </body> -</html> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list deleted file mode 100644 index 0bde2a8c8e3..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/contain/reftest.list +++ /dev/null @@ -1,54 +0,0 @@ -== contain-paint-clip-001.html contain-paint-clip-001-ref.html -== contain-paint-clip-002.html contain-paint-clip-002-ref.html -== contain-paint-clip-003.html contain-paint-clip-003-ref.html -== contain-paint-clip-004.html contain-paint-clip-004-ref.html -== contain-paint-clip-005.html contain-paint-clip-003-ref.html -== contain-paint-clip-006.html contain-paint-clip-006-ref.html -== contain-paint-containing-block-absolute-001.html contain-paint-containing-block-absolute-001-ref.html -== contain-paint-containing-block-fixed-001.html contain-paint-containing-block-fixed-001-ref.html -== contain-paint-formatting-context-float-001.html contain-paint-formatting-context-float-001-ref.html -== contain-paint-formatting-context-margin-001.html contain-paint-formatting-context-margin-001-ref.html -== contain-paint-ignored-cases-ib-split-001.html contain-paint-ignored-cases-ib-split-001-ref.html -== contain-paint-ignored-cases-internal-table-001a.html contain-paint-ignored-cases-internal-table-001-ref.html -== contain-paint-ignored-cases-internal-table-001b.html contain-paint-ignored-cases-internal-table-001-ref.html -== contain-paint-ignored-cases-no-principal-box-001.html contain-paint-ignored-cases-no-principal-box-001-ref.html -== contain-paint-ignored-cases-ruby-containing-block-001.html contain-paint-ignored-cases-ruby-containing-block-001-ref.html -== contain-paint-ignored-cases-ruby-stacking-and-clipping-001.html contain-paint-ignored-cases-ruby-stacking-and-clipping-001-ref.html -== contain-paint-stacking-context-001a.html contain-paint-stacking-context-001-ref.html -== contain-paint-stacking-context-001b.html contain-paint-stacking-context-001-ref.html -== contain-size-button-001.html contain-size-button-001-ref.html -== contain-size-block-001.html contain-size-block-001-ref.html -== contain-size-block-002.html contain-size-block-002-ref.html -== contain-size-block-003.html contain-size-block-003-ref.html -== contain-size-block-004.html contain-size-block-004-ref.html -== contain-size-inline-block-001.html contain-size-inline-block-001-ref.html -== contain-size-inline-block-002.html contain-size-inline-block-002-ref.html -== contain-size-inline-block-003.html contain-size-inline-block-003-ref.html -== contain-size-inline-block-004.html contain-size-inline-block-004-ref.html -== contain-size-flex-001.html contain-size-flex-001-ref.html -== contain-size-inline-flex-001.html contain-size-inline-flex-001-ref.html -== contain-size-grid-001.html contain-size-grid-001-ref.html -== contain-size-multicol-001.html contain-size-multicol-001-ref.html -== contain-size-fieldset-001.html contain-size-fieldset-001-ref.html -== contain-size-fieldset-002.html contain-size-fieldset-002-ref.html -== contain-size-multicol-002.html contain-size-multicol-002-ref.html -== contain-size-multicol-003.html contain-size-multicol-003-ref.html -== contain-size-select-elem-001.html contain-size-select-elem-001-ref.html -== contain-size-select-elem-002.html contain-size-select-elem-002-ref.html -== contain-size-select-elem-003.html contain-size-select-elem-003-ref.html -== contain-size-select-elem-004.html contain-size-select-elem-004-ref.html -== contain-size-select-elem-005.html contain-size-select-elem-005-ref.html -== contain-layout-overflow-001.html contain-layout-overflow-001-ref.html -== contain-layout-overflow-002.html contain-layout-overflow-002-ref.html -== contain-size-table-caption-001.html contain-size-table-caption-001-ref.html -== contain-layout-stacking-context-001.html contain-paint-stacking-context-001-ref.html -== contain-layout-formatting-context-float-001.html contain-paint-formatting-context-float-001-ref.html -== contain-layout-formatting-context-margin-001.html contain-layout-formatting-context-margin-001-ref.html -== contain-layout-containing-block-fixed-001.html contain-paint-containing-block-fixed-001-ref.html -== contain-layout-containing-block-absolute-001.html contain-paint-containing-block-absolute-001-ref.html -== contain-layout-ignored-cases-ib-split-001.html contain-layout-ignored-cases-ib-split-001-ref.html -== contain-layout-ignored-cases-no-principal-box-001.html contain-paint-ignored-cases-no-principal-box-001-ref.html -== contain-layout-ignored-cases-no-principal-box-002.html contain-layout-ignored-cases-no-principal-box-002-ref.html -== contain-layout-ignored-cases-no-principal-box-003.html contain-layout-ignored-cases-no-principal-box-003-ref.html -== contain-layout-suppress-baseline-001.html contain-layout-suppress-baseline-001-ref.html -== contain-layout-suppress-baseline-002.html contain-layout-suppress-baseline-002-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping-ref.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping-ref.html index 3924851b518..744aa328554 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping-ref.html +++ b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping-ref.html @@ -17,24 +17,8 @@ </style> <ul class="triangle"><li></ul> <ul class="triangle"><li></ul> -<ul class="triangle"><li></ul> -<ul class="hiragana"><li></ul> -<ul class="katakana"><li></ul> -<ul class="hiragana-iroha"><li></ul> -<ul class="katakana-iroha"><li></ul> -<ul class="hiragana"><li></ul> -<ul class="katakana"><li></ul> -<ul class="hiragana-iroha"><li></ul> -<ul class="katakana-iroha"><li></ul> <ol><li></ol> -<ol class="triangle"><li></ol> -<ol class="triangle"><li></ol> -<ol class="triangle"><li></ol> -<ol class="hiragana"><li></ol> -<ol class="katakana"><li></ol> -<ol class="hiragana-iroha"><li></ol> -<ol class="katakana-iroha"><li></ol> <ol class="hiragana"><li></ol> <ol class="katakana"><li></ol> <ol class="hiragana-iroha"><li></ol> @@ -43,7 +27,6 @@ <ul> <li class="triangle"> <li class="triangle"> - <li class="triangle"> <li class="hiragana"> <li class="katakana"> <li class="hiragana-iroha"> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html index 1e3fcfcf59b..6c53b42df81 100644 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html +++ b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/redefine-attr-mapping.html @@ -34,33 +34,16 @@ } </style> <ul type="circle"><li></ul> -<ul type="round"><li></ul> <ul type="square"><li></ul> -<ul type="i"><li></ul> -<ul type="I"><li></ul> -<ul type="a"><li></ul> -<ul type="A"><li></ul> -<ul type="lower-roman"><li></ul> -<ul type="upper-roman"><li></ul> -<ul type="lower-alpha"><li></ul> -<ul type="upper-alpha"><li></ul> <ol><li></ol> -<ol type="circle"><li></ol> -<ol type="round"><li></ol> -<ol type="square"><li></ol> <ol type="i"><li></ol> <ol type="I"><li></ol> <ol type="a"><li></ol> <ol type="A"><li></ol> -<ol type="lower-roman"><li></ol> -<ol type="upper-roman"><li></ol> -<ol type="lower-alpha"><li></ol> -<ol type="upper-alpha"><li></ol> <ul> <li type="circle"> - <li type="round"> <li type="square"> <li type="i"> <li type="I"> diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/reftest.list deleted file mode 100644 index 3a48ae4bdac..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/counter-styles-3/reftest.list +++ /dev/null @@ -1,35 +0,0 @@ -== system-cyclic.html system-cyclic-ref.html -== system-fixed.html system-fixed-ref.html -== system-symbolic.html system-symbolic-ref.html -== system-alphabetic.html system-alphabetic-ref.html -== system-numeric.html system-numeric-ref.html -== system-additive.html system-additive-ref.html -== system-extends.html system-extends-ref.html -== system-cyclic-invalid.html system-common-invalid-ref.html -== system-fixed-invalid.html system-common-invalid2-ref.html -== system-symbolic-invalid.html system-common-invalid-ref.html -== system-alphabetic-invalid.html system-common-invalid2-ref.html -== system-numeric-invalid.html system-common-invalid2-ref.html -== system-additive-invalid.html system-common-invalid-ref.html -== system-extends-invalid.html system-extends-invalid-ref.html -== descriptor-negative.html descriptor-negative-ref.html -== descriptor-prefix.html descriptor-prefix-ref.html -== descriptor-suffix.html descriptor-suffix-ref.html -== descriptor-range.html descriptor-range-ref.html -== descriptor-pad.html descriptor-pad-ref.html -== descriptor-fallback.html descriptor-fallback-ref.html -== descriptor-symbols.html descriptor-symbols-ref.html -== descriptor-negative-invalid.html descriptor-negative-invalid-ref.html -== descriptor-prefix-invalid.html descriptor-prefix-invalid-ref.html -== descriptor-suffix-invalid.html descriptor-suffix-invalid-ref.html -== descriptor-range-invalid.html descriptor-range-invalid-ref.html -== descriptor-pad-invalid.html descriptor-pad-invalid-ref.html -== descriptor-fallback-invalid.html descriptor-fallback-invalid-ref.html -== descriptor-symbols-invalid.html descriptor-symbols-invalid-ref.html -== name-case-sensitivity.html name-case-sensitivity-ref.html -== dependent-builtin.html dependent-builtin-ref.html -== redefine-builtin.html redefine-builtin-ref.html -== redefine-attr-mapping.html redefine-attr-mapping-ref.html -== disclosure-styles.html disclosure-styles-ref.html -== symbols-function.html symbols-function-ref.html -== symbols-function-invalid.html symbols-function-invalid-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/pagination/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/pagination/reftest.list deleted file mode 100644 index dee7aa24254..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/pagination/reftest.list +++ /dev/null @@ -1,43 +0,0 @@ -== moz-css21-block-page-break-inside-avoid-1.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-2.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-3.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-4.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-5.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-6.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-7.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-8.html moz-css21-block-page-break-inside-avoid-8-ref.html -== moz-css21-block-page-break-inside-avoid-9.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-10.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-11.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-12.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-block-page-break-inside-avoid-13.html moz-css21-block-page-break-inside-avoid-8-ref.html -== moz-css21-block-page-break-inside-avoid-14.html moz-css21-block-page-break-inside-avoid-14-ref.html -== moz-css21-block-page-break-inside-avoid-15.html moz-css21-block-page-break-inside-avoid-15-ref.html -== moz-css21-table-page-break-inside-avoid-1.html moz-css21-table-page-break-inside-avoid-ref.html -== moz-css21-table-page-break-inside-avoid-2.html moz-css21-table-page-break-inside-avoid-2-ref.html -== moz-css21-table-page-break-inside-avoid-3.html moz-css21-table-page-break-inside-avoid-3-ref.html -== moz-css21-table-page-break-inside-avoid-4.html moz-css21-table-page-break-inside-avoid-4-ref.html -== moz-css21-table-page-break-inside-avoid-5.html moz-css21-table-page-break-inside-avoid-5-ref.html -== moz-css21-table-page-break-inside-avoid-6.html moz-css21-table-page-break-inside-avoid-6-ref.html -== moz-css21-table-page-break-inside-avoid-7.html moz-css21-table-page-break-inside-avoid-7-ref.html -== moz-css21-table-page-break-inside-avoid-8.html moz-css21-table-page-break-inside-avoid-6-ref.html -== moz-css21-float-page-break-inside-avoid-1.html moz-css21-table-page-break-inside-avoid-ref.html -== moz-css21-float-page-break-inside-avoid-2.html moz-css21-float-page-break-inside-avoid-2-ref.html -== moz-css21-float-page-break-inside-avoid-3.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-float-page-break-inside-avoid-4.html moz-css21-block-page-break-inside-avoid-ref.html -== moz-css21-float-page-break-inside-avoid-5.html moz-css21-float-page-break-inside-avoid-5-ref.html -== moz-css21-float-page-break-inside-avoid-6.html moz-css21-float-page-break-inside-avoid-6-ref.html -== moz-css21-float-page-break-inside-avoid-7.html moz-css21-float-page-break-inside-avoid-7-ref.html -== moz-css21-float-page-break-inside-avoid-8.html moz-css21-float-page-break-inside-avoid-8-ref.html -== moz-css21-float-page-break-inside-avoid-9.html moz-css21-float-page-break-inside-avoid-9-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-1.html moz-css21-table-page-break-inside-avoid-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-2.html moz-css21-table-page-break-inside-avoid-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-3.html moz-css21-table-page-break-inside-avoid-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-4.html moz-css21-rowgroup-page-break-inside-avoid-4-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-5.html moz-css21-rowgroup-page-break-inside-avoid-5-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-6.html moz-css21-rowgroup-page-break-inside-avoid-5-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-7.html moz-css21-rowgroup-page-break-inside-avoid-7-ref.html -== moz-css21-rowgroup-page-break-inside-avoid-8.html moz-css21-rowgroup-page-break-inside-avoid-8-ref.html -== moz-css21-row-page-break-inside-avoid-1.html moz-css21-rowgroup-page-break-inside-avoid-5-ref.html -== moz-css21-row-page-break-inside-avoid-2.html moz-css21-rowgroup-page-break-inside-avoid-5-ref.html -== moz-css21-inline-page-break-inside-avoid-1.html moz-css21-inline-page-break-inside-avoid-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/reftest.list deleted file mode 100644 index 63e438aa9f4..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/reftest.list +++ /dev/null @@ -1,2 +0,0 @@ -include pagination/reftest.list -include replaced-sizing/reftest.list diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/replaced-sizing/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/replaced-sizing/reftest.list deleted file mode 100644 index b2af4ad08ab..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/css21/replaced-sizing/reftest.list +++ /dev/null @@ -1,9 +0,0 @@ -== replaced-elements-all-auto.html replaced-elements-all-auto-ref.html -== replaced-elements-height-20.html replaced-elements-height-20-ref.html -== replaced-elements-width-40.html replaced-elements-width-40-ref.html -== replaced-elements-min-height-20.html replaced-elements-all-auto-ref.html -== replaced-elements-min-width-40.html replaced-elements-all-auto-ref.html -== replaced-elements-min-height-40.html replaced-elements-min-height-40-ref.html -== replaced-elements-min-width-80.html replaced-elements-min-width-80-ref.html -== replaced-elements-max-height-20.html replaced-elements-height-20-ref.html -== replaced-elements-max-width-40.html replaced-elements-width-40-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/filters/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/filters/reftest.list deleted file mode 100644 index 36b5451867b..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/filters/reftest.list +++ /dev/null @@ -1,2 +0,0 @@ -== filter-containing-block-dynamic-1a.html filter-containing-block-dynamic-1-ref.html -== filter-containing-block-dynamic-1b.html filter-containing-block-dynamic-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/fonts3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/fonts3/reftest.list deleted file mode 100644 index b8900e86a6f..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/fonts3/reftest.list +++ /dev/null @@ -1,5 +0,0 @@ -== font-size-zero-1.html font-size-zero-1-ref.html -!= font-size-zero-1-ref.html font-size-zero-1-notref.html -== font-size-zero-2.html font-size-zero-2-ref.html -== font-size-adjust-zero-1.html font-size-zero-2-ref.html -== font-size-adjust-zero-2.html font-size-zero-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/reftest.list deleted file mode 100644 index 2cc553143c4..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/ib-split/reftest.list +++ /dev/null @@ -1,23 +0,0 @@ -== remove-split-inline-1.html remove-split-inline-1-ref.html -== remove-from-split-inline-1.html remove-from-split-inline-1-ref.html -== remove-from-split-inline-1-ref.html remove-from-split-inline-1-noib-ref.html -== remove-from-split-inline-2.html remove-from-split-inline-2-ref.html -== remove-from-split-inline-3.html remove-from-split-inline-3-ref.html -== remove-from-split-inline-3-ref.html remove-from-split-inline-3-noib-ref.html -== remove-from-split-inline-4.html remove-from-split-inline-4-ref.html -== remove-from-split-inline-4-ref.html remove-from-split-inline-4-noib-ref.html -== remove-from-split-inline-5.html remove-from-split-inline-5-ref.html -== remove-from-split-inline-5-ref.html remove-from-split-inline-5-noib-ref.html -== remove-from-split-inline-6.html remove-from-split-inline-6-ref.html -== remove-from-split-inline-6-ref.html remove-from-split-inline-6-noib-ref.html -== float-inside-inline-between-blocks-1.html float-inside-inline-between-blocks-1-ref.html -== table-pseudo-in-part3-1.html table-pseudo-in-part3-1-ref.html -== emptyspan-1.html emptyspan-1-ref.html -== emptyspan-2.html emptyspan-2-ref.html -== emptyspan-3.html emptyspan-3-ref.html -== emptyspan-4.html emptyspan-4-ref.html -== split-inner-inline-1.html split-inner-inline-1-ref.html -== split-inner-inline-2.html split-inner-inline-2-ref.html -== whitespace-present-1a.html whitespace-present-1-ref.html -== whitespace-present-1b.html whitespace-present-1-ref.html -== percent-height-1.html percent-height-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/reftest.list deleted file mode 100644 index f24ed762dfa..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/reftest.list +++ /dev/null @@ -1,199 +0,0 @@ -# Tests for dynamic change to aspect ratio on element with 'object-fit' set -== object-fit-dyn-aspect-ratio-001.html object-fit-dyn-aspect-ratio-001-ref.html -== object-fit-dyn-aspect-ratio-002.html object-fit-dyn-aspect-ratio-002-ref.html - -# Tests for 'object-fit' / 'object-position' with a PNG image -== object-fit-fill-png-001c.html object-fit-fill-png-001-ref.html -== object-fit-fill-png-001e.html object-fit-fill-png-001-ref.html -== object-fit-fill-png-001i.html object-fit-fill-png-001-ref.html -== object-fit-fill-png-001o.html object-fit-fill-png-001-ref.html -== object-fit-fill-png-001p.html object-fit-fill-png-001-ref.html -== object-fit-fill-png-002c.html object-fit-fill-png-002-ref.html -== object-fit-fill-png-002e.html object-fit-fill-png-002-ref.html -== object-fit-fill-png-002i.html object-fit-fill-png-002-ref.html -== object-fit-fill-png-002o.html object-fit-fill-png-002-ref.html -== object-fit-fill-png-002p.html object-fit-fill-png-002-ref.html -== object-fit-contain-png-001c.html object-fit-contain-png-001-ref.html -== object-fit-contain-png-001e.html object-fit-contain-png-001-ref.html -== object-fit-contain-png-001i.html object-fit-contain-png-001-ref.html -== object-fit-contain-png-001o.html object-fit-contain-png-001-ref.html -== object-fit-contain-png-001p.html object-fit-contain-png-001-ref.html -== object-fit-contain-png-002c.html object-fit-contain-png-002-ref.html -== object-fit-contain-png-002e.html object-fit-contain-png-002-ref.html -== object-fit-contain-png-002i.html object-fit-contain-png-002-ref.html -== object-fit-contain-png-002o.html object-fit-contain-png-002-ref.html -== object-fit-contain-png-002p.html object-fit-contain-png-002-ref.html -== object-fit-cover-png-001c.html object-fit-cover-png-001-ref.html -== object-fit-cover-png-001e.html object-fit-cover-png-001-ref.html -== object-fit-cover-png-001i.html object-fit-cover-png-001-ref.html -== object-fit-cover-png-001o.html object-fit-cover-png-001-ref.html -== object-fit-cover-png-001p.html object-fit-cover-png-001-ref.html -== object-fit-cover-png-002c.html object-fit-cover-png-002-ref.html -== object-fit-cover-png-002e.html object-fit-cover-png-002-ref.html -== object-fit-cover-png-002i.html object-fit-cover-png-002-ref.html -== object-fit-cover-png-002o.html object-fit-cover-png-002-ref.html -== object-fit-cover-png-002p.html object-fit-cover-png-002-ref.html -== object-fit-none-png-001c.html object-fit-none-png-001-ref.html -== object-fit-none-png-001e.html object-fit-none-png-001-ref.html -== object-fit-none-png-001i.html object-fit-none-png-001-ref.html -== object-fit-none-png-001o.html object-fit-none-png-001-ref.html -== object-fit-none-png-001p.html object-fit-none-png-001-ref.html -== object-fit-none-png-002c.html object-fit-none-png-002-ref.html -== object-fit-none-png-002e.html object-fit-none-png-002-ref.html -== object-fit-none-png-002i.html object-fit-none-png-002-ref.html -== object-fit-none-png-002o.html object-fit-none-png-002-ref.html -== object-fit-none-png-002p.html object-fit-none-png-002-ref.html -== object-fit-scale-down-png-001c.html object-fit-scale-down-png-001-ref.html -== object-fit-scale-down-png-001e.html object-fit-scale-down-png-001-ref.html -== object-fit-scale-down-png-001i.html object-fit-scale-down-png-001-ref.html -== object-fit-scale-down-png-001o.html object-fit-scale-down-png-001-ref.html -== object-fit-scale-down-png-001p.html object-fit-scale-down-png-001-ref.html -== object-fit-scale-down-png-002c.html object-fit-scale-down-png-002-ref.html -== object-fit-scale-down-png-002e.html object-fit-scale-down-png-002-ref.html -== object-fit-scale-down-png-002i.html object-fit-scale-down-png-002-ref.html -== object-fit-scale-down-png-002o.html object-fit-scale-down-png-002-ref.html -== object-fit-scale-down-png-002p.html object-fit-scale-down-png-002-ref.html - -# Tests for 'object-fit' / 'object-position' with an SVG image -== object-fit-fill-svg-001e.html object-fit-fill-svg-001-ref.html -== object-fit-fill-svg-001i.html object-fit-fill-svg-001-ref.html -== object-fit-fill-svg-001o.html object-fit-fill-svg-001-ref.html -== object-fit-fill-svg-001p.html object-fit-fill-svg-001-ref.html -== object-fit-fill-svg-002e.html object-fit-fill-svg-002-ref.html -== object-fit-fill-svg-002i.html object-fit-fill-svg-002-ref.html -== object-fit-fill-svg-002o.html object-fit-fill-svg-002-ref.html -== object-fit-fill-svg-002p.html object-fit-fill-svg-002-ref.html -== object-fit-fill-svg-003e.html object-fit-fill-svg-003-ref.html -== object-fit-fill-svg-003i.html object-fit-fill-svg-003-ref.html -== object-fit-fill-svg-003o.html object-fit-fill-svg-003-ref.html -== object-fit-fill-svg-003p.html object-fit-fill-svg-003-ref.html -== object-fit-fill-svg-004e.html object-fit-fill-svg-004-ref.html -== object-fit-fill-svg-004i.html object-fit-fill-svg-004-ref.html -== object-fit-fill-svg-004o.html object-fit-fill-svg-004-ref.html -== object-fit-fill-svg-004p.html object-fit-fill-svg-004-ref.html -== object-fit-fill-svg-005e.html object-fit-fill-svg-005-ref.html -== object-fit-fill-svg-005i.html object-fit-fill-svg-005-ref.html -== object-fit-fill-svg-005o.html object-fit-fill-svg-005-ref.html -== object-fit-fill-svg-005p.html object-fit-fill-svg-005-ref.html -== object-fit-fill-svg-006e.html object-fit-fill-svg-006-ref.html -== object-fit-fill-svg-006i.html object-fit-fill-svg-006-ref.html -== object-fit-fill-svg-006o.html object-fit-fill-svg-006-ref.html -== object-fit-fill-svg-006p.html object-fit-fill-svg-006-ref.html -# All the random-if(geckoview) failures in this file are tracked by bug 1558515 -== object-fit-contain-svg-001e.html object-fit-contain-svg-001-ref.html -== object-fit-contain-svg-001i.html object-fit-contain-svg-001-ref.html -== object-fit-contain-svg-001o.html object-fit-contain-svg-001-ref.html -== object-fit-contain-svg-001p.html object-fit-contain-svg-001-ref.html -== object-fit-contain-svg-002e.html object-fit-contain-svg-002-ref.html -== object-fit-contain-svg-002i.html object-fit-contain-svg-002-ref.html -== object-fit-contain-svg-002o.html object-fit-contain-svg-002-ref.html -== object-fit-contain-svg-002p.html object-fit-contain-svg-002-ref.html -== object-fit-contain-svg-003e.html object-fit-contain-svg-003-ref.html -== object-fit-contain-svg-003i.html object-fit-contain-svg-003-ref.html -== object-fit-contain-svg-003o.html object-fit-contain-svg-003-ref.html -== object-fit-contain-svg-003p.html object-fit-contain-svg-003-ref.html -== object-fit-contain-svg-004e.html object-fit-contain-svg-004-ref.html -== object-fit-contain-svg-004i.html object-fit-contain-svg-004-ref.html -== object-fit-contain-svg-004o.html object-fit-contain-svg-004-ref.html -== object-fit-contain-svg-004p.html object-fit-contain-svg-004-ref.html -== object-fit-contain-svg-005e.html object-fit-contain-svg-005-ref.html -== object-fit-contain-svg-005i.html object-fit-contain-svg-005-ref.html -== object-fit-contain-svg-005o.html object-fit-contain-svg-005-ref.html -== object-fit-contain-svg-005p.html object-fit-contain-svg-005-ref.html -== object-fit-contain-svg-006e.html object-fit-contain-svg-006-ref.html -== object-fit-contain-svg-006i.html object-fit-contain-svg-006-ref.html -== object-fit-contain-svg-006o.html object-fit-contain-svg-006-ref.html -== object-fit-contain-svg-006p.html object-fit-contain-svg-006-ref.html -== object-fit-cover-svg-001e.html object-fit-cover-svg-001-ref.html -== object-fit-cover-svg-001i.html object-fit-cover-svg-001-ref.html -== object-fit-cover-svg-001o.html object-fit-cover-svg-001-ref.html -== object-fit-cover-svg-001p.html object-fit-cover-svg-001-ref.html -== object-fit-cover-svg-002e.html object-fit-cover-svg-002-ref.html -== object-fit-cover-svg-002i.html object-fit-cover-svg-002-ref.html -== object-fit-cover-svg-002o.html object-fit-cover-svg-002-ref.html -== object-fit-cover-svg-002p.html object-fit-cover-svg-002-ref.html -== object-fit-cover-svg-003e.html object-fit-cover-svg-003-ref.html -== object-fit-cover-svg-003i.html object-fit-cover-svg-003-ref.html -== object-fit-cover-svg-003o.html object-fit-cover-svg-003-ref.html -== object-fit-cover-svg-003p.html object-fit-cover-svg-003-ref.html -== object-fit-cover-svg-004e.html object-fit-cover-svg-004-ref.html -== object-fit-cover-svg-004i.html object-fit-cover-svg-004-ref.html -== object-fit-cover-svg-004o.html object-fit-cover-svg-004-ref.html -== object-fit-cover-svg-004p.html object-fit-cover-svg-004-ref.html -== object-fit-cover-svg-005e.html object-fit-cover-svg-005-ref.html -== object-fit-cover-svg-005i.html object-fit-cover-svg-005-ref.html -== object-fit-cover-svg-005o.html object-fit-cover-svg-005-ref.html -== object-fit-cover-svg-005p.html object-fit-cover-svg-005-ref.html -== object-fit-cover-svg-006e.html object-fit-cover-svg-006-ref.html -== object-fit-cover-svg-006i.html object-fit-cover-svg-006-ref.html -== object-fit-cover-svg-006o.html object-fit-cover-svg-006-ref.html -== object-fit-cover-svg-006p.html object-fit-cover-svg-006-ref.html -== object-fit-none-svg-001e.html object-fit-none-svg-001-ref.html -== object-fit-none-svg-001i.html object-fit-none-svg-001-ref.html -== object-fit-none-svg-001o.html object-fit-none-svg-001-ref.html -== object-fit-none-svg-001p.html object-fit-none-svg-001-ref.html -== object-fit-none-svg-002e.html object-fit-none-svg-002-ref.html -== object-fit-none-svg-002i.html object-fit-none-svg-002-ref.html -== object-fit-none-svg-002o.html object-fit-none-svg-002-ref.html -== object-fit-none-svg-002p.html object-fit-none-svg-002-ref.html -== object-fit-none-svg-003e.html object-fit-none-svg-003-ref.html -== object-fit-none-svg-003i.html object-fit-none-svg-003-ref.html -== object-fit-none-svg-003o.html object-fit-none-svg-003-ref.html -== object-fit-none-svg-003p.html object-fit-none-svg-003-ref.html -== object-fit-none-svg-004e.html object-fit-none-svg-004-ref.html -== object-fit-none-svg-004i.html object-fit-none-svg-004-ref.html -== object-fit-none-svg-004o.html object-fit-none-svg-004-ref.html -== object-fit-none-svg-004p.html object-fit-none-svg-004-ref.html -== object-fit-none-svg-005e.html object-fit-none-svg-005-ref.html -== object-fit-none-svg-005i.html object-fit-none-svg-005-ref.html -== object-fit-none-svg-005o.html object-fit-none-svg-005-ref.html -== object-fit-none-svg-005p.html object-fit-none-svg-005-ref.html -== object-fit-none-svg-006e.html object-fit-none-svg-006-ref.html -== object-fit-none-svg-006i.html object-fit-none-svg-006-ref.html -== object-fit-none-svg-006o.html object-fit-none-svg-006-ref.html -== object-fit-none-svg-006p.html object-fit-none-svg-006-ref.html -== object-fit-scale-down-svg-001e.html object-fit-scale-down-svg-001-ref.html -== object-fit-scale-down-svg-001i.html object-fit-scale-down-svg-001-ref.html -== object-fit-scale-down-svg-001o.html object-fit-scale-down-svg-001-ref.html -== object-fit-scale-down-svg-001p.html object-fit-scale-down-svg-001-ref.html -== object-fit-scale-down-svg-002e.html object-fit-scale-down-svg-002-ref.html -== object-fit-scale-down-svg-002i.html object-fit-scale-down-svg-002-ref.html -== object-fit-scale-down-svg-002o.html object-fit-scale-down-svg-002-ref.html -== object-fit-scale-down-svg-002p.html object-fit-scale-down-svg-002-ref.html -== object-fit-scale-down-svg-003e.html object-fit-scale-down-svg-003-ref.html -== object-fit-scale-down-svg-003i.html object-fit-scale-down-svg-003-ref.html -== object-fit-scale-down-svg-003o.html object-fit-scale-down-svg-003-ref.html -== object-fit-scale-down-svg-003p.html object-fit-scale-down-svg-003-ref.html -== object-fit-scale-down-svg-004e.html object-fit-scale-down-svg-004-ref.html -== object-fit-scale-down-svg-004i.html object-fit-scale-down-svg-004-ref.html -== object-fit-scale-down-svg-004o.html object-fit-scale-down-svg-004-ref.html -== object-fit-scale-down-svg-004p.html object-fit-scale-down-svg-004-ref.html -== object-fit-scale-down-svg-005e.html object-fit-scale-down-svg-005-ref.html -== object-fit-scale-down-svg-005i.html object-fit-scale-down-svg-005-ref.html -== object-fit-scale-down-svg-005o.html object-fit-scale-down-svg-005-ref.html -== object-fit-scale-down-svg-005p.html object-fit-scale-down-svg-005-ref.html -== object-fit-scale-down-svg-006e.html object-fit-scale-down-svg-006-ref.html -== object-fit-scale-down-svg-006i.html object-fit-scale-down-svg-006-ref.html -== object-fit-scale-down-svg-006o.html object-fit-scale-down-svg-006-ref.html -== object-fit-scale-down-svg-006p.html object-fit-scale-down-svg-006-ref.html -== object-position-png-001c.html object-position-png-001-ref.html -== object-position-png-001e.html object-position-png-001-ref.html -== object-position-png-001i.html object-position-png-001-ref.html -== object-position-png-001o.html object-position-png-001-ref.html -== object-position-png-001p.html object-position-png-001-ref.html -== object-position-png-002c.html object-position-png-002-ref.html -== object-position-png-002e.html object-position-png-002-ref.html -== object-position-png-002i.html object-position-png-002-ref.html -== object-position-png-002o.html object-position-png-002-ref.html -== object-position-png-002p.html object-position-png-002-ref.html -== object-position-svg-001e.html object-position-svg-001-ref.html -== object-position-svg-001i.html object-position-svg-001-ref.html -== object-position-svg-001o.html object-position-svg-001-ref.html -== object-position-svg-001p.html object-position-svg-001-ref.html -== object-position-svg-002e.html object-position-svg-002-ref.html -== object-position-svg-002i.html object-position-svg-002-ref.html -== object-position-svg-002o.html object-position-svg-002-ref.html -== object-position-svg-002p.html object-position-svg-002-ref.html - -# Tests for gradient color stops with 'currentcolor' -== color-stop-currentcolor.html color-stop-currentcolor-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-fit-and-position-canvas-tests.sh b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-fit-and-position-canvas-tests.sh deleted file mode 100644 index b66ea472695..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/images3/support/generate-object-fit-and-position-canvas-tests.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash -# -# Any copyright is dedicated to the Public Domain. -# http://creativecommons.org/publicdomain/zero/1.0/ -# -# Script to generate <canvas src> reftest files for "object-fit" and -# "object-position", from corresponding reftest files that use <object>. -# -# This script expects to be run from this working directory: -# mozilla-central/layout/reftests/w3c-css/submitted/images3 - -# Array of image files that we'll use -imageFileArr=("support/colors-16x8.png" "support/colors-8x16.png") -canvasAttributeArr=('width="16" height="8"' 'width="8" height="16"') -numImageFiles=${#imageFileArr[@]} - - -for ((i = 0; i < $numImageFiles; i++)); do - - imageFile=${imageFileArr[$i]} - canvasAttrs=${canvasAttributeArr[$i]} - - # Loop across <object> tests: - # (We assume that tests that end with "001" use the first PNG image in - # $imageFileArr, and tests that end with "002" use the second PNG image.) - let testNum=$i+1 - for origTestName in object-*-png-*00${testNum}o.html; do - # Find the corresponding reference case: - origReferenceName=$(echo $origTestName | - sed "s/o.html/-ref.html/") - - # Replace "o" suffix in filename with "c" (for "canvas") - canvasTestName=$(echo $origTestName | - sed "s/o.html/c.html/") - - # Generate testcase - # (converting <object data="..."> to <canvas width="..." height="..."> - echo "Generating $canvasTestName from $origTestName." - hg cp $origTestName $canvasTestName - - # Do string-replacements in testcase to convert it to test canvas: - # Adjust html & body nodes: - sed -i "s|<html>|<html class=\"reftest-wait\">|" $canvasTestName - sed -i "s|<body>|<body onload=\"drawImageToCanvases('$imageFile')\">|" $canvasTestName - # Adjust <title>: - sed -i "s|object element|canvas element|g" $canvasTestName - # Tweak the actual tags (open & close tags, and CSS rule): - sed -i "s|object {|canvas {|" $canvasTestName - sed -i "s|<object|<canvas|" $canvasTestName - sed -i "s|</object>|</canvas>|" $canvasTestName - # Drop "data" attr (pointing to image URI) and replace with - # width/height attrs to establish the canvas's intrinsic size: - sed -i "s|data=\"$imageFile\"|$canvasAttrs|" $canvasTestName - - # Add a <script> block to draw an image into each canvas: - sed -i "/<\/style>/a \\ - <script>\n\ - function drawImageToCanvases(imageURI) {\n\ - var image = new Image();\n\ - image.onload = function() {\n\ - var canvasElems = document.getElementsByTagName(\"canvas\");\n\ - for (var i = 0; i < canvasElems.length; i++) {\n\ - var ctx = canvasElems[i].getContext(\"2d\");\n\ - ctx.drawImage(image, 0, 0);\n\ - }\n\ - document.documentElement.removeAttribute(\"class\");\n\ - }\n\ - image.src = imageURI;\n\ - }\n\ - <\/script>" $canvasTestName - done -done diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/masking/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/masking/reftest.list deleted file mode 100644 index 3777f46a87c..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/masking/reftest.list +++ /dev/null @@ -1,117 +0,0 @@ -# For those test items with failure type fuzzy-if added, please refer to bug 1231643#c10. - -# mask-composite test cases -== mask-composite-1a.html mask-composite-1-ref.html -== mask-composite-1b.html mask-composite-1-ref.html -== mask-composite-1c.html mask-composite-1-ref.html -== mask-composite-1d.html mask-composite-1-ref.html -== mask-composite-2a.html mask-composite-2-ref.html -== mask-composite-2b.html mask-composite-2-ref.html -== mask-composite-2c.html mask-composite-2-ref.html - -# mask-mode test cases -== mask-mode-a.html mask-mode-ref.html -== mask-mode-b.html mask-mode-ref.html -== mask-mode-c.html mask-mode-ref.html -== mask-mode-d.html mask-mode-ref.html -== mask-mode-to-mask-type.html mask-mode-to-mask-type-ref.html - -# mask-image test cases -== mask-image-1a.html mask-image-1-ref.html -== mask-image-1b.html mask-image-1-ref.html -== mask-image-1c.html mask-image-1-ref.html -== mask-image-1d.html mask-image-1-ref.html -== mask-image-2.html mask-image-2-ref.html -== mask-image-3a.html mask-image-3-ref.html -== mask-image-3b.html mask-image-3-ref.html -== mask-image-3c.html mask-image-3-ref.html -== mask-image-3d.html mask-image-3-ref.html -== mask-image-3e.html mask-image-3-ref.html -# Due to SVG luminance, see bug 1372577, parent process doesn't use d2d for luminance. -== mask-image-3f.html mask-image-3-ref.html -== mask-image-3g.html mask-image-3-ref.html -== mask-image-3h.html mask-image-3-ref.html -== mask-image-3i.html mask-image-3-ref.html -== mask-image-4a.html blank.html -== mask-image-4b.html blank.html -== mask-image-5.html mask-image-5-ref.html -== mask-image-6.html mask-image-6-ref.html - -# mask-clip test cases -== mask-clip-1.html mask-clip-1-ref.html -== mask-clip-2.html mask-clip-2-ref.html - -# mask-position test cases -== mask-position-1a.html mask-position-1-ref.html -== mask-position-1b.html mask-position-1-ref.html -== mask-position-1c.html mask-position-1-ref.html -== mask-position-2a.html mask-position-2-ref.html -== mask-position-2b.html mask-position-2-ref.html -== mask-position-3a.html mask-position-3-ref.html -== mask-position-3b.html mask-position-3-ref.html -== mask-position-4a.html mask-position-4-ref.html -== mask-position-4b.html mask-position-4-ref.html -== mask-position-4c.html mask-position-4-ref.html -== mask-position-4d.html mask-position-4-ref.html -== mask-position-5.html mask-position-5-ref.html -== mask-position-6.html mask-position-6-ref.html -== mask-position-7.html mask-position-7-ref.html - -# mask-repeat test cases -== mask-repeat-1.html mask-repeat-1-ref.html -== mask-repeat-2.html mask-repeat-2-ref.html -== mask-repeat-3.html mask-repeat-3-ref.html - -# mask-origin test cases -== mask-origin-1.html mask-origin-1-ref.html -== mask-origin-2.html mask-origin-2-ref.html -== mask-origin-3.html mask-origin-3-ref.html - -# mask-size test cases -== mask-size-auto.html mask-size-auto-ref.html -== mask-size-auto-auto.html mask-size-auto-ref.html -== mask-size-auto-length.html mask-size-auto-length-ref.html -== mask-size-auto-percent.html mask-size-auto-length-ref.html -== mask-size-contain-clip-border.html mask-size-contain-clip-border-ref.html -== mask-size-contain-clip-padding.html mask-size-contain-clip-padding-ref.html -== mask-size-contain-position-fifty-fifty.html mask-size-contain-position-fifty-fifty-ref.html -== mask-size-contain.html mask-size-contain-ref.html -== mask-size-cover.html mask-size-cover-ref.html -== mask-size-length.html mask-size-length-length-ref.html -== mask-size-length-auto.html mask-size-length-length-ref.html -== mask-size-length-length.html mask-size-length-length-ref.html -== mask-size-length-percent.html mask-size-length-percent-ref.html -== mask-size-percent.html mask-size-percent-percent-ref.html -== mask-size-percent-auto.html mask-size-percent-percent-ref.html -== mask-size-percent-length.html mask-size-percent-percent-ref.html -== mask-size-percent-percent.html mask-size-percent-percent-ref.html -== mask-size-percent-percent-stretch.html mask-size-percent-percent-stretch-ref.html - -== clip-path-contentBox-1a.html clip-path-geometryBox-1-ref.html -== clip-path-contentBox-1b.html clip-path-geometryBox-1-ref.html -== clip-path-contentBox-1c.html clip-path-geometryBox-1-ref.html -== clip-path-paddingBox-1a.html clip-path-geometryBox-1-ref.html -== clip-path-paddingBox-1b.html clip-path-geometryBox-1-ref.html -== clip-path-paddingBox-1c.html clip-path-geometryBox-1-ref.html -== clip-path-borderBox-1a.html clip-path-geometryBox-1-ref.html -== clip-path-borderBox-1b.html clip-path-geometryBox-1-ref.html -== clip-path-borderBox-1c.html clip-path-geometryBox-1-ref.html -== clip-path-marginBox-1a.html clip-path-geometryBox-1-ref.html -== clip-path-fillBox-1a.html clip-path-geometryBox-1-ref.html -== clip-path-strokeBox-1a.html clip-path-geometryBox-1-ref.html -== clip-path-strokeBox-1b.html clip-path-geometryBox-1-ref.html -== clip-path-viewBox-1a.html clip-path-geometryBox-1-ref.html -== clip-path-viewBox-1b.html clip-path-geometryBox-1-ref.html -== clip-path-viewBox-1c.html clip-path-geometryBox-1-ref.html -== clip-path-geometryBox-2.html clip-path-geometryBox-2-ref.html - -== clip-path-localRef-1.html clip-path-localRef-1-ref.html - -# mask with opacity test cases -== mask-opacity-1a.html mask-opacity-1-ref.html -== mask-opacity-1b.html mask-opacity-1-ref.html -== mask-opacity-1c.html mask-opacity-1-ref.html -== mask-opacity-1d.html mask-opacity-1-ref.html -== mask-opacity-1e.html mask-opacity-1-ref.html - -== clip-path-mix-blend-mode-1.html clip-path-mix-blend-mode-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/reftest.list deleted file mode 100644 index 5b5efb0412b..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/reftest.list +++ /dev/null @@ -1,68 +0,0 @@ -# Do not assign a test to multiple references. Chain the references instead. -# See README. - -== test-template-001.xht references/test-template-001.xht - -# Module includes, alphabetical sans "CSS". Add/uncomment as necessary. - -## CSS Snapshot 2007 - -# CSS2.1 -include css21/reftest.list - -# Backgrounds and Borders -include background/reftest.list - -# Fragmentation -include break3/reftest.list - -# Color Level 4 -include color4/reftest.list - -# Containment -include contain/reftest.list - -# Counter Styles Level 3 -include counter-styles-3/reftest.list - -# Filter Effects Module -include filters/reftest.list - -# Fonts Level 3 -include fonts3/reftest.list - -# block-inside-inline splits -include ib-split/reftest.list - -# Image Values and Replaced Content Level 3 -include images3/reftest.list - -# Masking Level 1 -include masking/reftest.list - -# Ruby Layout Module -include ruby/reftest.list - -# Selectors Level 4 -include selectors4/reftest.list - -# CSS Intrinsic & Extrinsic Sizing Module Level 3 -include sizing/reftest.list - -# Text Level 3 -include text3/reftest.list - -# Text Decoration Level 3 -include text-decor-3/reftest.list - -# Values and Units Level 3 -include values3/reftest.list - -# Variables Level 1 -include variables/reftest.list - -# CSS will-change Level 1 -include will-change/reftest.list - -# CSS Writing Modes Level 3 -include writing-modes-3/reftest.list diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/ruby/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/ruby/reftest.list deleted file mode 100644 index 25447e5e872..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/ruby/reftest.list +++ /dev/null @@ -1,21 +0,0 @@ -# Tests for inlinizing block-level boxes -== ruby-inlinize-blocks-001.html ruby-inlinize-blocks-001-ref.html -== ruby-inlinize-blocks-002.html ruby-inlinize-blocks-002-ref.html -== ruby-inlinize-blocks-003.html ruby-inlinize-blocks-003-ref.html -== ruby-inlinize-blocks-004.html ruby-inlinize-blocks-004-ref.html -== ruby-inlinize-blocks-005.html ruby-inlinize-blocks-005-ref.html - -# Tests for autohiding base-identical annotations -== ruby-autohide-001.html ruby-autohide-001-ref.html -== ruby-autohide-002.html ruby-autohide-002-ref.html -== ruby-autohide-003.html ruby-autohide-003-ref.html -== ruby-autohide-004.html ruby-autohide-001-ref.html - -# Tests for ruby with text-combine-upright -== ruby-text-combine-upright-001a.html ruby-text-combine-upright-001-ref.html -== ruby-text-combine-upright-001b.html ruby-text-combine-upright-001-ref.html -== ruby-text-combine-upright-002a.html ruby-text-combine-upright-002-ref.html -== ruby-text-combine-upright-002b.html ruby-text-combine-upright-002-ref.html - -# Tests for nested ruby -== nested-ruby-pairing-001.html nested-ruby-pairing-001-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/reftest.list deleted file mode 100644 index 4e0cf10d5ae..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/selectors4/reftest.list +++ /dev/null @@ -1,12 +0,0 @@ -== focus-within-1.html focus-within-1-ref.html -== focus-within-2.html focus-within-2-ref.html -== focus-within-3.html focus-within-3-ref.html -== dir-style-01a.html dir-style-01-ref.html -== dir-style-01b.html dir-style-01-ref.html -== dir-style-02a.html dir-style-02-ref.html -== dir-style-02b.html dir-style-02-ref.html -== dir-style-03a.html dir-style-03-ref.html -== dir-style-03b.html dir-style-03-ref.html -== dir-style-04.html dir-style-04-ref.html -== child-index-no-parent-01.html child-index-no-parent-01-ref.html -== class-id-attr-selector-invalidation-01.html class-id-attr-selector-invalidation-01-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/reftest.list deleted file mode 100644 index 1952f3253b6..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/sizing/reftest.list +++ /dev/null @@ -1,10 +0,0 @@ -== block-size-with-min-or-max-content-1a.html block-size-with-min-or-max-content-1-ref.html -== block-size-with-min-or-max-content-1b.html block-size-with-min-or-max-content-1-ref.html -== block-size-with-min-or-max-content-table-1a.html block-size-with-min-or-max-content-table-1-ref.html -== block-size-with-min-or-max-content-table-1b.html block-size-with-min-or-max-content-table-1-ref.html -== hori-block-size-small-or-larger-than-container-with-min-or-max-content-1.html hori-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html -== hori-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html hori-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html -== hori-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html hori-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html -== vert-block-size-small-or-larger-than-container-with-min-or-max-content-1.html vert-block-size-small-or-larger-than-container-with-min-or-max-content-1-ref.html -== vert-block-size-small-or-larger-than-container-with-min-or-max-content-2a.html vert-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html -== vert-block-size-small-or-larger-than-container-with-min-or-max-content-2b.html vert-block-size-small-or-larger-than-container-with-min-or-max-content-2-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/text-decor-3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/text-decor-3/reftest.list deleted file mode 100644 index 42f029ce03d..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/text-decor-3/reftest.list +++ /dev/null @@ -1,135 +0,0 @@ -== ruby-text-decoration-01.html ruby-text-decoration-01-ref.html -== text-decoration-propagation-01.html text-decoration-propagation-01-ref.html -== text-decoration-propagation-dynamic-001.html text-decoration-propagation-dynamic-001-ref.html - -# text-emphasis-style -== text-emphasis-style-property-001.html text-emphasis-style-property-001-ref.html -== text-emphasis-style-property-002.html text-emphasis-style-property-002-ref.html -== text-emphasis-style-property-003.html text-emphasis-style-property-003-ref.html -== text-emphasis-style-property-004.html text-emphasis-style-property-004-ref.html -== text-emphasis-style-property-005.html text-emphasis-style-property-005-ref.html -== text-emphasis-style-property-005a.html text-emphasis-style-property-005-ref.html -# START tests from support/generate-text-emphasis-style-property-010-tests.sh -== text-emphasis-style-property-010Zs.html text-emphasis-style-property-010-ref.html -== text-emphasis-style-property-010Zl.html text-emphasis-style-property-010-ref.html -== text-emphasis-style-property-010Zp.html text-emphasis-style-property-010-ref.html -== text-emphasis-style-property-010Cc.html text-emphasis-style-property-010-ref.html -== text-emphasis-style-property-010Cf.html text-emphasis-style-property-010-ref.html -# END tests from support/generate-text-emphasis-style-property-010-tests.sh -== text-emphasis-style-property-010Cn.html text-emphasis-style-property-010-ref.html -# START tests from support/generate-text-emphasis-style-property-tests.py -== text-emphasis-style-property-011.html text-emphasis-style-property-011-ref.html -== text-emphasis-style-property-011a.html text-emphasis-style-property-011-ref.html -== text-emphasis-style-property-011b.html text-emphasis-style-property-011-ref.html -== text-emphasis-style-property-012.html text-emphasis-style-property-012-ref.html -== text-emphasis-style-property-012a.html text-emphasis-style-property-012-ref.html -== text-emphasis-style-property-012b.html text-emphasis-style-property-012-ref.html -== text-emphasis-style-property-012c.html text-emphasis-style-property-012-ref.html -== text-emphasis-style-property-013.html text-emphasis-style-property-013-ref.html -== text-emphasis-style-property-013a.html text-emphasis-style-property-013-ref.html -== text-emphasis-style-property-013b.html text-emphasis-style-property-013-ref.html -== text-emphasis-style-property-014.html text-emphasis-style-property-014-ref.html -== text-emphasis-style-property-014a.html text-emphasis-style-property-014-ref.html -== text-emphasis-style-property-014b.html text-emphasis-style-property-014-ref.html -== text-emphasis-style-property-015.html text-emphasis-style-property-015-ref.html -== text-emphasis-style-property-015a.html text-emphasis-style-property-015-ref.html -== text-emphasis-style-property-015b.html text-emphasis-style-property-015-ref.html -== text-emphasis-style-property-016.html text-emphasis-style-property-016-ref.html -== text-emphasis-style-property-016a.html text-emphasis-style-property-016-ref.html -== text-emphasis-style-property-017.html text-emphasis-style-property-017-ref.html -== text-emphasis-style-property-017a.html text-emphasis-style-property-017-ref.html -== text-emphasis-style-property-017b.html text-emphasis-style-property-017-ref.html -== text-emphasis-style-property-018.html text-emphasis-style-property-018-ref.html -== text-emphasis-style-property-018a.html text-emphasis-style-property-018-ref.html -== text-emphasis-style-property-019.html text-emphasis-style-property-019-ref.html -== text-emphasis-style-property-019a.html text-emphasis-style-property-019-ref.html -== text-emphasis-style-property-020.html text-emphasis-style-property-020-ref.html -== text-emphasis-style-property-020a.html text-emphasis-style-property-020-ref.html -# END tests from support/generate-text-emphasis-style-property-tests.py - -# text-emphasis-color -== text-emphasis-color-property-001.html text-emphasis-color-property-001-ref.html -== text-emphasis-color-property-001a.html text-emphasis-color-property-001-ref.html -== text-emphasis-color-property-002.html text-emphasis-color-property-002-ref.html - -# text-emphasis -== text-emphasis-property-001.html text-emphasis-style-property-001-ref.html -== text-emphasis-property-002.html text-emphasis-style-property-002-ref.html -== text-emphasis-property-003.html text-emphasis-style-property-012-ref.html -== text-emphasis-property-003a.html text-emphasis-style-property-012-ref.html -== text-emphasis-property-003b.html text-emphasis-style-property-012-ref.html -== text-emphasis-property-004.html text-emphasis-color-property-002-ref.html -== text-emphasis-property-004a.html text-emphasis-color-property-002-ref.html - -# text-emphasis-position -# START tests from support/generate-text-emphasis-position-property-tests.py -== text-emphasis-position-property-001.html text-emphasis-position-property-001-ref.html -== text-emphasis-position-property-001a.html text-emphasis-position-property-001-ref.html -== text-emphasis-position-property-001b.html text-emphasis-position-property-001-ref.html -== text-emphasis-position-property-001c.html text-emphasis-position-property-001-ref.html -== text-emphasis-position-property-002.html text-emphasis-position-property-002-ref.html -== text-emphasis-position-property-002a.html text-emphasis-position-property-002-ref.html -== text-emphasis-position-property-002b.html text-emphasis-position-property-002-ref.html -== text-emphasis-position-property-002c.html text-emphasis-position-property-002-ref.html -== text-emphasis-position-property-003.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-003a.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-003b.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-003c.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-003d.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-003e.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-003f.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-003g.html text-emphasis-position-property-003-ref.html -== text-emphasis-position-property-004.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-004a.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-004b.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-004c.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-004d.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-004e.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-004f.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-004g.html text-emphasis-position-property-004-ref.html -== text-emphasis-position-property-005.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-005a.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-005b.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-005c.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-005d.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-005e.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-005f.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-005g.html text-emphasis-position-property-005-ref.html -== text-emphasis-position-property-006.html text-emphasis-position-property-006-ref.html -== text-emphasis-position-property-006a.html text-emphasis-position-property-006-ref.html -== text-emphasis-position-property-006b.html text-emphasis-position-property-006-ref.html -== text-emphasis-position-property-006c.html text-emphasis-position-property-006-ref.html -== text-emphasis-position-property-006d.html text-emphasis-position-property-006-ref.html -== text-emphasis-position-property-006e.html text-emphasis-position-property-006-ref.html -== text-emphasis-position-property-006f.html text-emphasis-position-property-006-ref.html -== text-emphasis-position-property-006g.html text-emphasis-position-property-006-ref.html -# END tests from support/generate-text-emphasis-position-property-tests.py - -# START tests from support/generate-text-emphasis-ruby-tests.py -== text-emphasis-ruby-001.html text-emphasis-ruby-001-ref.html -== text-emphasis-ruby-002.html text-emphasis-ruby-002-ref.html -== text-emphasis-ruby-003.html text-emphasis-ruby-003-ref.html -== text-emphasis-ruby-003a.html text-emphasis-ruby-003-ref.html -== text-emphasis-ruby-004.html text-emphasis-ruby-004-ref.html -== text-emphasis-ruby-004a.html text-emphasis-ruby-004-ref.html -# END tests from support/generate-text-emphasis-ruby-tests.py - -# text-emphasis line height -# START tests from support/generate-text-emphasis-line-height-tests.py -== text-emphasis-line-height-001a.html text-emphasis-line-height-001-ref.html -== text-emphasis-line-height-001b.html text-emphasis-line-height-001-ref.html -== text-emphasis-line-height-002a.html text-emphasis-line-height-002-ref.html -== text-emphasis-line-height-002b.html text-emphasis-line-height-002-ref.html -== text-emphasis-line-height-003a.html text-emphasis-line-height-003-ref.html -== text-emphasis-line-height-003b.html text-emphasis-line-height-003-ref.html -== text-emphasis-line-height-003c.html text-emphasis-line-height-003-ref.html -== text-emphasis-line-height-003d.html text-emphasis-line-height-003-ref.html -== text-emphasis-line-height-004a.html text-emphasis-line-height-004-ref.html -== text-emphasis-line-height-004b.html text-emphasis-line-height-004-ref.html -== text-emphasis-line-height-004c.html text-emphasis-line-height-004-ref.html -== text-emphasis-line-height-004d.html text-emphasis-line-height-004-ref.html -# END tests from support/generate-text-emphasis-line-height-tests.py -== text-emphasis-line-height-001z.html text-emphasis-line-height-001-ref.html - -# text-shadow 'currentcolor' test cases -== text-shadow-currentcolor.html text-shadow-currentcolor-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/text3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/text3/reftest.list deleted file mode 100644 index e5135ba565b..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/text3/reftest.list +++ /dev/null @@ -1,74 +0,0 @@ -== text-align-match-parent-01.html text-align-match-parent-ref.html -== text-align-match-parent-02.html text-align-match-parent-ref.html -== text-align-match-parent-03.html text-align-match-parent-ref.html -== text-align-match-parent-04.html text-align-match-parent-ref.html -== text-align-match-parent-root-ltr.html text-align-match-parent-root-ltr-ref.html -== text-align-match-parent-root-rtl.html text-align-match-parent-root-rtl-ref.html - -== text-justify-none-001.html text-justify-none-001-ref.html -== text-justify-inter-word-001.html text-justify-inter-word-001-ref.html -== text-justify-inter-character-001.html text-justify-inter-character-001-ref.html -== text-justify-distribute-001.html text-justify-inter-character-001-ref.html - -== text-word-spacing-001.html text-word-spacing-ref.html - -== hyphenation-control-1.html hyphenation-control-1-ref.html - -== segment-break-transformation-removable-1.html segment-break-transformation-removable-ref.html -== segment-break-transformation-removable-2.html segment-break-transformation-removable-ref.html -== segment-break-transformation-removable-3.html segment-break-transformation-removable-ref.html -== segment-break-transformation-removable-4.html segment-break-transformation-removable-ref.html -== segment-break-transformation-unremovable-1.html segment-break-transformation-unremovable-ref.html -== segment-break-transformation-unremovable-2.html segment-break-transformation-unremovable-ref.html -== segment-break-transformation-unremovable-3.html segment-break-transformation-unremovable-ref.html -== segment-break-transformation-unremovable-4.html segment-break-transformation-unremovable-ref.html - -== segment-break-transformation-rules-001.html segment-break-transformation-rules-001-ref.html -== segment-break-transformation-rules-002.html segment-break-transformation-rules-002-ref.html -== segment-break-transformation-rules-003.html segment-break-transformation-rules-003-ref.html -== segment-break-transformation-rules-004.html segment-break-transformation-rules-004-ref.html -== segment-break-transformation-rules-005.html segment-break-transformation-rules-005-ref.html -== segment-break-transformation-rules-006.html segment-break-transformation-rules-006-ref.html -== segment-break-transformation-rules-007.html segment-break-transformation-rules-007-ref.html -== segment-break-transformation-rules-008.html segment-break-transformation-rules-008-ref.html -== segment-break-transformation-rules-009.html segment-break-transformation-rules-009-ref.html -== segment-break-transformation-rules-010.html segment-break-transformation-rules-010-ref.html -== segment-break-transformation-rules-011.html segment-break-transformation-rules-011-ref.html -== segment-break-transformation-rules-012.html segment-break-transformation-rules-012-ref.html -== segment-break-transformation-rules-013.html segment-break-transformation-rules-013-ref.html -== segment-break-transformation-rules-014.html segment-break-transformation-rules-014-ref.html -== segment-break-transformation-rules-015.html segment-break-transformation-rules-015-ref.html -== segment-break-transformation-rules-016.html segment-break-transformation-rules-016-ref.html -== segment-break-transformation-rules-017.html segment-break-transformation-rules-017-ref.html -== segment-break-transformation-rules-018.html segment-break-transformation-rules-018-ref.html -== segment-break-transformation-rules-019.html segment-break-transformation-rules-019-ref.html -== segment-break-transformation-rules-020.html segment-break-transformation-rules-020-ref.html -== segment-break-transformation-rules-021.html segment-break-transformation-rules-021-ref.html -== segment-break-transformation-rules-022.html segment-break-transformation-rules-022-ref.html -== segment-break-transformation-rules-023.html segment-break-transformation-rules-023-ref.html -== segment-break-transformation-rules-024.html segment-break-transformation-rules-024-ref.html -== segment-break-transformation-rules-025.html segment-break-transformation-rules-025-ref.html -== segment-break-transformation-rules-026.html segment-break-transformation-rules-026-ref.html -== segment-break-transformation-rules-027.html segment-break-transformation-rules-027-ref.html -== segment-break-transformation-rules-028.html segment-break-transformation-rules-028-ref.html -== segment-break-transformation-rules-029.html segment-break-transformation-rules-029-ref.html -== segment-break-transformation-rules-030.html segment-break-transformation-rules-030-ref.html -== segment-break-transformation-rules-031.html segment-break-transformation-rules-031-ref.html -== segment-break-transformation-rules-032.html segment-break-transformation-rules-032-ref.html -== segment-break-transformation-rules-033.html segment-break-transformation-rules-033-ref.html -== segment-break-transformation-rules-034.html segment-break-transformation-rules-034-ref.html -== segment-break-transformation-rules-035.html segment-break-transformation-rules-035-ref.html -== segment-break-transformation-rules-036.html segment-break-transformation-rules-036-ref.html -== segment-break-transformation-rules-037.html segment-break-transformation-rules-037-ref.html -== segment-break-transformation-rules-038.html segment-break-transformation-rules-038-ref.html -== segment-break-transformation-rules-039.html segment-break-transformation-rules-039-ref.html -== segment-break-transformation-rules-040.html segment-break-transformation-rules-040-ref.html -== segment-break-transformation-rules-041.html segment-break-transformation-rules-041-ref.html -== segment-break-transformation-rules-042.html segment-break-transformation-rules-042-ref.html -== segment-break-transformation-rules-043.html segment-break-transformation-rules-043-ref.html -== segment-break-transformation-rules-044.html segment-break-transformation-rules-044-ref.html -== segment-break-transformation-rules-045.html segment-break-transformation-rules-045-ref.html -== segment-break-transformation-rules-046.html segment-break-transformation-rules-046-ref.html -== segment-break-transformation-rules-047.html segment-break-transformation-rules-047-ref.html -== segment-break-transformation-rules-048.html segment-break-transformation-rules-048-ref.html -== segment-break-transformation-rules-049.html segment-break-transformation-rules-049-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/reftest.list deleted file mode 100644 index 87562d963de..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/values3/reftest.list +++ /dev/null @@ -1,32 +0,0 @@ -== calc-background-linear-gradient-1.html calc-background-linear-gradient-1-ref.html -== calc-background-image-gradient-1.html calc-background-image-gradient-1-ref.html -== calc-background-position-1.html calc-background-position-1-ref.html -== calc-background-size-1.html calc-background-size-1-ref.html -== calc-border-radius-1.html calc-border-radius-1-ref.html -== calc-height-block-1.html calc-height-block-1-ref.html -== calc-height-table-1.html calc-height-table-1-ref.html -== calc-margin-block-1.html calc-margin-block-1-ref.html -== calc-max-height-block-1.html calc-max-height-block-1-ref.html -== calc-max-width-block-1.html calc-width-block-1-ref.html -== calc-max-width-block-intrinsic-1.html calc-max-width-block-intrinsic-1-ref.html -== calc-min-height-block-1.html calc-height-block-1-ref.html -== calc-min-width-block-1.html calc-width-block-1-ref.html -== calc-min-width-block-intrinsic-1.html calc-min-width-block-intrinsic-1-ref.html -== calc-offsets-absolute-bottom-1.html calc-offsets-absolute-top-1-ref.html -== calc-offsets-absolute-left-1.html calc-offsets-relative-left-1-ref.html -== calc-offsets-absolute-right-1.html calc-offsets-relative-left-1-ref.html -== calc-offsets-absolute-top-1.html calc-offsets-absolute-top-1-ref.html -== calc-offsets-relative-bottom-1.html calc-offsets-relative-top-1-ref.html -== calc-offsets-relative-left-1.html calc-offsets-relative-left-1-ref.html -== calc-offsets-relative-right-1.html calc-offsets-relative-left-1-ref.html -== calc-offsets-relative-top-1.html calc-offsets-relative-top-1-ref.html -== calc-padding-block-1.html calc-padding-block-1-ref.html -== calc-text-indent-1.html calc-text-indent-1-ref.html -== calc-text-indent-intrinsic-1.html calc-text-indent-intrinsic-1-ref.html -== calc-transform-origin-1.html calc-transform-origin-1-ref.html -== calc-vertical-align-1.html calc-vertical-align-1-ref.html -== calc-width-block-1.html calc-width-block-1-ref.html -== calc-width-block-intrinsic-1.html calc-width-block-intrinsic-1-ref.html -== calc-width-table-auto-1.html calc-width-table-auto-1-ref.html -== calc-width-table-fixed-1.html calc-width-table-fixed-1-ref.html -== rem-root-font-size-restyle-1.html rem-root-font-size-restyle-1-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/reftest.list deleted file mode 100644 index baa90e4e900..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/variables/reftest.list +++ /dev/null @@ -1,172 +0,0 @@ -== variable-declaration-01.html support/color-green-ref.html -== variable-declaration-02.html support/color-green-ref.html -== variable-declaration-03.html support/color-green-ref.html -== variable-declaration-04.html support/color-green-ref.html -== variable-declaration-05.html support/color-green-ref.html -== variable-declaration-06.html support/color-green-ref.html -== variable-declaration-07.html support/color-green-ref.html -== variable-declaration-08.html support/color-green-ref.html -== variable-declaration-09.html support/color-green-ref.html -== variable-declaration-10.html support/color-green-ref.html -== variable-declaration-11.html support/color-green-ref.html -== variable-declaration-12.html support/color-green-ref.html -== variable-declaration-13.html support/color-green-ref.html -== variable-declaration-14.html support/color-green-ref.html -== variable-declaration-15.html variable-declaration-15-ref.html -== variable-declaration-16.html variable-declaration-16-ref.html -== variable-declaration-17.html variable-declaration-17-ref.html -== variable-declaration-18.html variable-declaration-18-ref.html -== variable-declaration-19.html support/color-green-ref.html -== variable-declaration-20.html support/color-green-ref.html -== variable-declaration-21.html support/color-green-ref.html -== variable-declaration-22.html support/color-green-ref.html -== variable-declaration-23.html support/color-green-ref.html -== variable-declaration-24.html support/color-green-ref.html -== variable-declaration-25.html support/color-green-ref.html -== variable-declaration-26.html support/color-green-ref.html -== variable-declaration-28.html support/color-green-ref.html -== variable-declaration-29.html support/color-green-ref.html -== variable-declaration-30.html support/color-green-ref.html -== variable-declaration-31.html support/color-green-ref.html -== variable-declaration-32.html support/color-green-ref.html -== variable-declaration-33.html support/color-green-ref.html -== variable-declaration-34.html support/color-green-ref.html -== variable-declaration-35.html support/color-green-ref.html -== variable-declaration-36.html support/color-green-ref.html -== variable-declaration-37.html support/color-green-ref.html -== variable-declaration-38.html support/color-green-ref.html -== variable-declaration-39.html support/color-green-ref.html -== variable-declaration-40.html support/color-green-ref.html -== variable-declaration-41.html support/color-green-ref.html -== variable-declaration-42.html support/color-green-ref.html -== variable-declaration-43.html support/color-green-ref.html -== variable-declaration-44.html support/color-green-ref.html -== variable-declaration-45.html support/color-green-ref.html -== variable-declaration-46.html support/color-green-ref.html -== variable-declaration-47.html support/color-green-ref.html -== variable-declaration-48.html support/color-green-ref.html -== variable-declaration-49.html support/color-green-ref.html -== variable-declaration-50.html support/color-green-ref.html -== variable-declaration-51.html support/color-green-ref.html -== variable-declaration-52.html support/color-green-ref.html -== variable-declaration-53.html support/color-green-ref.html -== variable-declaration-54.html support/color-green-ref.html -== variable-declaration-55.html support/color-green-ref.html -== variable-declaration-56.html support/color-green-ref.html -== variable-declaration-57.html support/color-green-ref.html -== variable-declaration-58.html support/color-green-ref.html -== variable-declaration-59.html support/color-green-ref.html -== variable-declaration-60.html support/color-green-ref.html -== variable-external-declaration-01.html support/color-green-ref.html -== variable-external-font-face-01.html variable-external-font-face-01-ref.html -== variable-external-reference-01.html support/color-green-ref.html -== variable-external-supports-01.html support/color-green-ref.html -== variable-font-face-01.html variable-font-face-01-ref.html -== variable-font-face-02.html variable-font-face-02-ref.html -== variable-reference-01.html support/color-green-ref.html -== variable-reference-02.html support/color-green-ref.html -== variable-reference-03.html support/color-green-ref.html -== variable-reference-04.html support/color-green-ref.html -== variable-reference-05.html support/color-green-ref.html -== variable-reference-06.html support/color-green-ref.html -== variable-reference-07.html support/color-green-ref.html -== variable-reference-08.html support/color-green-ref.html -== variable-reference-09.html support/color-green-ref.html -== variable-reference-10.html support/color-green-ref.html -== variable-reference-11.html support/color-green-ref.html -== variable-reference-12.html variable-reference-12-ref.html -== variable-reference-13.html support/color-green-ref.html -== variable-reference-14.html support/color-green-ref.html -== variable-reference-15.html support/color-green-ref.html -== variable-reference-16.html support/color-green-ref.html -== variable-reference-17.html support/color-green-ref.html -== variable-reference-18.html support/color-green-ref.html -== variable-reference-19.html support/color-green-ref.html -== variable-reference-20.html support/color-green-ref.html -== variable-reference-21.html support/color-green-ref.html -== variable-reference-22.html support/color-green-ref.html -== variable-reference-23.html support/color-green-ref.html -== variable-reference-24.html support/color-green-ref.html -== variable-reference-25.html support/color-green-ref.html -== variable-reference-26.html support/color-green-ref.html -== variable-reference-27.html support/color-green-ref.html -== variable-reference-28.html support/color-green-ref.html -== variable-reference-29.html support/color-green-ref.html -== variable-reference-30.html support/color-green-ref.html -== variable-reference-31.html support/color-green-ref.html -== variable-reference-32.html support/color-green-ref.html -== variable-reference-33.html support/color-green-ref.html -== variable-reference-34.html support/color-green-ref.html -== variable-reference-35.html support/color-green-ref.html -== variable-reference-36.html variable-reference-36-ref.html -== variable-reference-37.html variable-reference-37-ref.html -== variable-reference-38.html variable-reference-38-ref.html -== variable-reference-39.html support/color-green-ref.html -== variable-reference-40.html variable-reference-40-ref.html -== variable-supports-01.html support/color-green-ref.html -== variable-supports-02.html support/color-green-ref.html -== variable-supports-03.html support/color-green-ref.html -== variable-supports-04.html support/color-green-ref.html -== variable-supports-05.html support/color-green-ref.html -== variable-supports-06.html support/color-green-ref.html -== variable-supports-07.html support/color-green-ref.html -== variable-supports-08.html support/color-green-ref.html -== variable-supports-09.html support/color-green-ref.html -== variable-supports-10.html support/color-green-ref.html -== variable-supports-11.html support/color-green-ref.html -== variable-supports-12.html support/color-green-ref.html -== variable-supports-13.html support/color-green-ref.html -== variable-supports-14.html support/color-green-ref.html -== variable-supports-15.html support/color-green-ref.html -== variable-supports-16.html support/color-green-ref.html -== variable-supports-17.html support/color-green-ref.html -== variable-supports-18.html support/color-green-ref.html -== variable-supports-19.html support/color-green-ref.html -== variable-supports-20.html support/color-green-ref.html -== variable-supports-21.html support/color-green-ref.html -== variable-supports-22.html support/color-green-ref.html -== variable-supports-23.html support/color-green-ref.html -== variable-supports-24.html support/color-green-ref.html -== variable-supports-25.html support/color-green-ref.html -== variable-supports-26.html support/color-green-ref.html -== variable-supports-27.html support/color-green-ref.html -== variable-supports-28.html support/color-green-ref.html -== variable-supports-29.html support/color-green-ref.html -== variable-supports-30.html support/color-green-ref.html -== variable-supports-31.html support/color-green-ref.html -== variable-supports-32.html support/color-green-ref.html -== variable-supports-33.html support/color-green-ref.html -== variable-supports-34.html support/color-green-ref.html -== variable-supports-35.html support/color-green-ref.html -== variable-supports-36.html support/color-green-ref.html -== variable-supports-37.html support/color-green-ref.html -== variable-supports-38.html support/color-green-ref.html -== variable-supports-39.html support/color-green-ref.html -== variable-supports-40.html support/color-green-ref.html -== variable-supports-41.html support/color-green-ref.html -== variable-supports-42.html support/color-green-ref.html -== variable-supports-43.html support/color-green-ref.html -== variable-supports-44.html support/color-green-ref.html -== variable-supports-45.html support/color-green-ref.html -== variable-supports-46.html support/color-green-ref.html -== variable-supports-47.html support/color-green-ref.html -== variable-supports-48.html support/color-green-ref.html -== variable-supports-49.html support/color-green-ref.html -== variable-supports-50.html support/color-green-ref.html -== variable-supports-51.html support/color-green-ref.html -== variable-supports-52.html support/color-green-ref.html -== variable-supports-53.html support/color-green-ref.html -== variable-supports-54.html support/color-green-ref.html -== variable-supports-55.html support/color-green-ref.html -== variable-supports-56.html support/color-green-ref.html -== variable-supports-57.html support/color-green-ref.html -== variable-supports-58.html support/color-green-ref.html -== variable-supports-59.html support/color-green-ref.html -== variable-supports-60.html support/color-green-ref.html -== variable-supports-61.html support/color-green-ref.html -== variable-supports-62.html support/color-green-ref.html -== variable-supports-63.html support/color-green-ref.html -== variable-supports-64.html support/color-green-ref.html -== variable-supports-65.html support/color-green-ref.html -== variable-supports-66.html support/color-green-ref.html -== variable-supports-67.html support/color-green-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/will-change/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/will-change/reftest.list deleted file mode 100644 index 231532eac32..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/will-change/reftest.list +++ /dev/null @@ -1,23 +0,0 @@ -== will-change-stacking-context-clip-path-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-filter-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-height-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-isolation-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-mask-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-mix-blend-mode-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-opacity-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-perspective-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-position-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-transform-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-translate-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-offset-path-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-transform-style-1.html green-square-100-by-100-ref.html -== will-change-stacking-context-z-index-1.html green-square-100-by-100-ref.html -== will-change-fixpos-cb-contain-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-filter-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-height-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-perspective-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-position-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-transform-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-translate-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-offset-path-1.html green-square-100-by-100-offset-ref.html -== will-change-fixpos-cb-transform-style-1.html green-square-100-by-100-offset-ref.html diff --git a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/reftest.list b/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/reftest.list deleted file mode 100644 index be4eea1fabb..00000000000 --- a/tests/wpt/web-platform-tests/css/vendor-imports/mozilla/mozilla-central-reftests/writing-modes-3/reftest.list +++ /dev/null @@ -1,22 +0,0 @@ -== text-combine-upright-break-inside-001.html text-combine-upright-break-inside-001-ref.html -== text-combine-upright-break-inside-001a.html text-combine-upright-break-inside-001-ref.html -== text-combine-upright-compression-001.html text-combine-upright-compression-001-ref.html -== text-combine-upright-compression-002.html text-combine-upright-compression-002-ref.html -== text-combine-upright-compression-003.html text-combine-upright-compression-003-ref.html -== text-combine-upright-compression-004.html text-combine-upright-compression-004-ref.html -== text-combine-upright-compression-005.html text-combine-upright-compression-005-ref.html -== text-combine-upright-compression-005a.html text-combine-upright-compression-005-ref.html -== text-combine-upright-compression-006.html text-combine-upright-compression-006-ref.html -== text-combine-upright-compression-006a.html text-combine-upright-compression-006-ref.html -== text-combine-upright-compression-007.html text-combine-upright-compression-007-ref.html - -== text-orientation-upright-directionality-001.html text-orientation-upright-directionality-001-ref.html - -== logical-physical-mapping-001.html logical-physical-mapping-001-ref.html - -== dynamic-offset-rtl-001.html dynamic-offset-rtl-001-ref.html -== dynamic-offset-rtl-002.html dynamic-offset-rtl-001-ref.html -== dynamic-offset-vrl-001.html dynamic-offset-rtl-001-ref.html -== dynamic-offset-vrl-002.html dynamic-offset-rtl-001-ref.html -== dynamic-offset-vrl-rtl-001.html dynamic-offset-rtl-001-ref.html -== dynamic-offset-vrl-rtl-002.html dynamic-offset-rtl-001-ref.html diff --git a/tests/wpt/web-platform-tests/docs/commands.json b/tests/wpt/web-platform-tests/docs/commands.json index f3494b8cd93..55408d83dbc 100644 --- a/tests/wpt/web-platform-tests/docs/commands.json +++ b/tests/wpt/web-platform-tests/docs/commands.json @@ -3,7 +3,6 @@ "path": "frontend.py", "script": "build", "help": "Build documentation", - "py3only": true, "virtualenv": true, "requirements": [ "./requirements.txt" diff --git a/tests/wpt/web-platform-tests/docs/writing-tests/python-handlers/index.md b/tests/wpt/web-platform-tests/docs/writing-tests/python-handlers/index.md index 96f7edb6160..53fa8009c21 100644 --- a/tests/wpt/web-platform-tests/docs/writing-tests/python-handlers/index.md +++ b/tests/wpt/web-platform-tests/docs/writing-tests/python-handlers/index.md @@ -67,20 +67,6 @@ import importlib myhelper = importlib.import_module('common.security-features.myhelper') ``` -**Note on __init__ files**: Until WPT moves to Python 3 only (~Q1 2021), -importing helper scripts like this requires a 'path' of empty `__init__.py` -files in every directory down to the helper. For example, if your helper is -`css/css-align/resources/myhelper.py`, you need to have: - -``` -css/__init__.py -css/css-align/__init__.py -css/css-align/resources/__init__.py -``` - -This requirement will be removed once we move to Python 3, due to -[PEP 420](https://www.python.org/dev/peps/pep-0420/). - ## Example: Dynamic HTTP headers The following code defines a Python handler that allows the requester to diff --git a/tests/wpt/web-platform-tests/dom/events/Event-dispatch-redispatch.html b/tests/wpt/web-platform-tests/dom/events/Event-dispatch-redispatch.html index 7869aa00de8..cf861ca1774 100644 --- a/tests/wpt/web-platform-tests/dom/events/Event-dispatch-redispatch.html +++ b/tests/wpt/web-platform-tests/dom/events/Event-dispatch-redispatch.html @@ -105,13 +105,7 @@ async function testMouseUpAndClickEvent() { await waitForLoad; let bounds = buttonElement.getBoundingClientRect(); test(() => { assert_true(true); }, `Synthesizing click on button...`); - new test_driver.Actions() - .pointerMove(Math.floor(bounds.width / 5), - Math.floor(bounds.height / 2), - {origin: buttonElement}) - .pointerDown({button: test_driver.Actions.prototype.ButtonType.LEFT}) - .pointerUp({button: test_driver.Actions.prototype.ButtonType.LEFT}) - .send() + new test_driver.click(buttonElement) .then(() => { test_mouseup_redispatching.step(() => { assert_not_equals(clickEvent, undefined, "mouseup and click events should've been fired"); diff --git a/tests/wpt/web-platform-tests/encoding/textdecoder-arguments.any.js b/tests/wpt/web-platform-tests/encoding/textdecoder-arguments.any.js new file mode 100644 index 00000000000..f469dcd30ea --- /dev/null +++ b/tests/wpt/web-platform-tests/encoding/textdecoder-arguments.any.js @@ -0,0 +1,49 @@ +// META: title=Encoding API: TextDecoder decode() optional arguments + +test(t => { + const decoder = new TextDecoder(); + + // Just passing nothing. + assert_equals( + decoder.decode(undefined), '', + 'Undefined as first arg should decode to empty string'); + + // Flushing an incomplete sequence. + decoder.decode(new Uint8Array([0xc9]), {stream: true}); + assert_equals( + decoder.decode(undefined), '\uFFFD', + 'Undefined as first arg should flush the stream'); + +}, 'TextDecoder decode() with explicit undefined'); + +test(t => { + const decoder = new TextDecoder(); + + // Just passing nothing. + assert_equals( + decoder.decode(undefined, undefined), '', + 'Undefined as first arg should decode to empty string'); + + // Flushing an incomplete sequence. + decoder.decode(new Uint8Array([0xc9]), {stream: true}); + assert_equals( + decoder.decode(undefined, undefined), '\uFFFD', + 'Undefined as first arg should flush the stream'); + +}, 'TextDecoder decode() with undefined and undefined'); + +test(t => { + const decoder = new TextDecoder(); + + // Just passing nothing. + assert_equals( + decoder.decode(undefined, {}), '', + 'Undefined as first arg should decode to empty string'); + + // Flushing an incomplete sequence. + decoder.decode(new Uint8Array([0xc9]), {stream: true}); + assert_equals( + decoder.decode(undefined, {}), '\uFFFD', + 'Undefined as first arg should flush the stream'); + +}, 'TextDecoder decode() with undefined and options'); diff --git a/tests/wpt/web-platform-tests/entries-api/support.js b/tests/wpt/web-platform-tests/entries-api/support.js index 79720ce56b4..1cf3ad95b9a 100644 --- a/tests/wpt/web-platform-tests/entries-api/support.js +++ b/tests/wpt/web-platform-tests/entries-api/support.js @@ -11,15 +11,14 @@ window.addEventListener('DOMContentLoaded', e => { document.body.appendChild(header); const elem = document.createElement('div'); elem.style.cssText = 'height: 50px; border: 1px dotted red;'; - elem.innerHTML = 'Drop the <b>support/upload</b> directory here.</div>'; + elem.innerHTML = 'Drop or paste the <b>support/upload</b> directory here.</div>'; document.body.appendChild(elem); elem.addEventListener('dragover', e => { e.preventDefault(); }); - elem.addEventListener('drop', e => { - e.preventDefault(); - for (let i = 0; i < e.dataTransfer.items.length; ++i) { - const item = e.dataTransfer.items[i]; + const onDropOrPaste = dataTransfer => { + for (let i = 0; i < dataTransfer.items.length; ++i) { + const item = dataTransfer.items[i]; if (item.kind !== 'file') continue; const entry = item.webkitGetAsEntry(); @@ -27,6 +26,14 @@ window.addEventListener('DOMContentLoaded', e => { tests.forEach(f => f(entry, item)); break; } + }; + elem.addEventListener('drop', e => { + e.preventDefault(); + onDropOrPaste(e.dataTransfer); + }); + elem.addEventListener('paste', e => { + e.preventDefault(); + onDropOrPaste(e.clipboardData); }); }); diff --git a/tests/wpt/web-platform-tests/event-timing/crossiframe.html b/tests/wpt/web-platform-tests/event-timing/crossiframe.html index 147e144631d..6beb9aa357f 100644 --- a/tests/wpt/web-platform-tests/event-timing/crossiframe.html +++ b/tests/wpt/web-platform-tests/event-timing/crossiframe.html @@ -72,10 +72,13 @@ }); const childFrameEntriesPromise = new Promise(resolve => { window.addEventListener("message", (event) => { - t.step(() => { - validateChildFrameEntries(event.data); - }); - resolve(); + // testdriver-complete is a webdriver internal event + if (event.data.type != "testdriver-complete") { + t.step(() => { + validateChildFrameEntries(event.data); + }); + resolve(); + } }, false); }); let iframe = document.getElementById('iframe'); diff --git a/tests/wpt/web-platform-tests/event-timing/mouseenter.html b/tests/wpt/web-platform-tests/event-timing/mouseenter.html index 804d5743742..1d0171ec460 100644 --- a/tests/wpt/web-platform-tests/event-timing/mouseenter.html +++ b/tests/wpt/web-platform-tests/event-timing/mouseenter.html @@ -12,7 +12,8 @@ <div id='target'>Target</div> <script> promise_test(async t => { - return testEventType(t, 'mouseenter'); + // PointerMove also creates mouseenter events on the body + return testEventType(t, 'mouseenter', true); }) </script> </html> diff --git a/tests/wpt/web-platform-tests/event-timing/mouseleave.html b/tests/wpt/web-platform-tests/event-timing/mouseleave.html index 202cf73e2a6..b3cf1447e38 100644 --- a/tests/wpt/web-platform-tests/event-timing/mouseleave.html +++ b/tests/wpt/web-platform-tests/event-timing/mouseleave.html @@ -12,7 +12,8 @@ <div id='target'>Target</div> <script> promise_test(async t => { - return testEventType(t, 'mouseleave'); + // Loose event because a mouseleave from html -> body also occurs + return testEventType(t, 'mouseleave', true); }) </script> </html> diff --git a/tests/wpt/web-platform-tests/event-timing/pointerenter.html b/tests/wpt/web-platform-tests/event-timing/pointerenter.html index bae1c51d51c..bb8ee08abc7 100644 --- a/tests/wpt/web-platform-tests/event-timing/pointerenter.html +++ b/tests/wpt/web-platform-tests/event-timing/pointerenter.html @@ -12,7 +12,9 @@ <div id='target'>Target</div> <script> promise_test(async t => { - return testEventType(t, 'pointerenter'); + // A looseCount because the first move of pointerenter causes a + // `pointerenter` on body + return testEventType(t, 'pointerenter', true); }) </script> </html> diff --git a/tests/wpt/web-platform-tests/event-timing/pointerleave.html b/tests/wpt/web-platform-tests/event-timing/pointerleave.html index 81e8f15ec0d..b17464e0e7b 100644 --- a/tests/wpt/web-platform-tests/event-timing/pointerleave.html +++ b/tests/wpt/web-platform-tests/event-timing/pointerleave.html @@ -12,7 +12,8 @@ <div id='target'>Target</div> <script> promise_test(async t => { - return testEventType(t, 'pointerleave'); + // Loose event because a pointerleave from html -> body also occurs + return testEventType(t, 'pointerleave', true); }) </script> </html> diff --git a/tests/wpt/web-platform-tests/event-timing/resources/event-timing-test-utils.js b/tests/wpt/web-platform-tests/event-timing/resources/event-timing-test-utils.js index 749cfe3c19d..70fd66abfbc 100644 --- a/tests/wpt/web-platform-tests/event-timing/resources/event-timing-test-utils.js +++ b/tests/wpt/web-platform-tests/event-timing/resources/event-timing-test-utils.js @@ -70,6 +70,13 @@ function clickAndBlockMain(id) { }); } +function waitForTick() { + return new Promise(resolve => { + window.requestAnimationFrame(() => { + window.requestAnimationFrame(resolve); + }); + }); +} // Add a PerformanceObserver and observe with a durationThreshold of |dur|. This test will // attempt to check that the duration is appropriately checked by: // * Asserting that entries received have a duration which is the smallest multiple of 8 @@ -150,11 +157,12 @@ function applyAction(eventType, target) { // Reset by clicking outside of the target. .pointerMove(0, 0) .pointerDown() - .pointerUp(); } else if (eventType === 'mouseenter' || eventType === 'mouseover' || eventType === 'pointerenter' || eventType === 'pointerover') { // Move outside of the target and then back inside. - actions.pointerMove(0, 0) + // Moving it to 0, 1 because 0, 0 doesn't cause the pointer to + // move in Firefox. See https://github.com/w3c/webdriver/issues/1545 + actions.pointerMove(0, 1) .pointerMove(0, 0, {origin: target}); } else if (eventType === 'mouseleave' || eventType === 'mouseout' || eventType === 'pointerleave' || eventType === 'pointerout') { @@ -219,6 +227,7 @@ async function testEventType(t, eventType, looseCount=false) { // Trigger two 'fast' events of the type. await applyAction(eventType, target); await applyAction(eventType, target); + await waitForTick(); await new Promise(t.step_func(resolve => { testCounts(t, resolve, looseCount, eventType, initialCount + 2); })); @@ -260,6 +269,9 @@ async function testEventType(t, eventType, looseCount=false) { })).observe({type: 'event', durationThreshold: durationThreshold}); }); // Cause a slow event. - let actionPromise = applyAction(eventType, target); - return Promise.all([actionPromise, observerPromise]); + await applyAction(eventType, target); + + await waitForTick(); + + await observerPromise; } diff --git a/tests/wpt/web-platform-tests/event-timing/shadow-dom-null-target.html b/tests/wpt/web-platform-tests/event-timing/shadow-dom-null-target.html new file mode 100644 index 00000000000..89587312c89 --- /dev/null +++ b/tests/wpt/web-platform-tests/event-timing/shadow-dom-null-target.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html> +<meta charset=utf-8 /> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src=/resources/testdriver-actions.js></script> +<script src=resources/event-timing-test-utils.js></script> +<body> +<div id='host'> +</div> +<script> +promise_test(async t => { + assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.'); + + const host = document.getElementById('host'); + const shadowRoot = host.attachShadow({mode: "closed"}); + + const durationThreshold = 16; + + const innerSpan1 = document.createElement("span"); + innerSpan1.textContent = "Span1"; + const innerSpan2 = document.createElement("span"); + innerSpan2.textContent = "Span2"; + + shadowRoot.append(innerSpan1); + shadowRoot.append(innerSpan2); + + innerSpan2.addEventListener("mouseover", function(e) { + // To make this is a slow event + mainThreadBusy(durationThreshold + 4); + }); + + const span1Rect = innerSpan1.getBoundingClientRect(); + const span2Rect = innerSpan2.getBoundingClientRect(); + + const actions = new test_driver.Actions(); + // Move the pointer to innerSpan1 + await actions.pointerMove( + Math.ceil(span1Rect.x + span1Rect.width / 2), + Math.ceil(span1Rect.y + span1Rect.height / 2) + ).send(); + + await waitForTick(); + + const observerPromise = new Promise(resolve => { + new PerformanceObserver(t.step_func(entryList => { + let mouseOverEntries = entryList.getEntriesByName("mouseover"); + assert_equals(mouseOverEntries.length, 1); + assert_equals(mouseOverEntries[0].target, null); + resolve(); + })).observe({type: 'event', durationThreshold: durationThreshold}); + }); + + // Move the pointer to innerSpan2 + // Here we move the pointer within the shadow DOM + await actions.pointerMove( + Math.ceil(span2Rect.x + span2Rect.width / 2), + Math.ceil(span2Rect.y + span2Rect.height / 2) + ).send(); + + await waitForTick(); + + await observerPromise; +}, "Event Timing: Move pointer within shadow DOM should create event-timing entry with null target."); +</script> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/fetch/api/basic/keepalive.html b/tests/wpt/web-platform-tests/fetch/api/basic/keepalive.html index 447ef2ddfec..36d156bba43 100644 --- a/tests/wpt/web-platform-tests/fetch/api/basic/keepalive.html +++ b/tests/wpt/web-platform-tests/fetch/api/basic/keepalive.html @@ -7,8 +7,15 @@ <script src="/common/get-host-info.sub.js"></script> <body> <script> + +const { + HTTP_NOTSAMESITE_ORIGIN, + HTTP_REMOTE_ORIGIN, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT +} = get_host_info(); + function getUrl(origin1, origin2, withHeaders) { - const frameOrigin = host_info.HTTP_NOTSAMESITE_ORIGIN; + const frameOrigin = HTTP_NOTSAMESITE_ORIGIN; return `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` + `origin1=${origin1}&` + `origin2=${origin2}&` + @@ -35,7 +42,7 @@ async function queryToken(token) { // an async_test. function checkToken(testName, token) { async_test((test) => { - new Promise((resolve) => test.step_timeout(resolve, 1000)).then(() => { + new Promise((resolve) => test.step_timeout(resolve, 3000)).then(() => { return queryToken(token); }).then((result) => { assert_equals(result, 'on'); @@ -47,7 +54,6 @@ function checkToken(testName, token) { }, testName); } -const host_info = get_host_info(); promise_test(async (test) => { const iframe = document.createElement('iframe'); iframe.src = getUrl('', '', false); @@ -62,8 +68,8 @@ promise_test(async (test) => { promise_test(async (test) => { const iframe = document.createElement('iframe'); - iframe.src = getUrl(host_info.HTTP_REMOTE_ORIGIN, - host_info.HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, false); + iframe.src = getUrl(HTTP_REMOTE_ORIGIN, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, false); document.body.appendChild(iframe); const tokenPromise = getToken(); await (new Promise((resolve) => iframe.addEventListener('load', resolve))); @@ -75,8 +81,8 @@ promise_test(async (test) => { promise_test(async (test) => { const iframe = document.createElement('iframe'); - iframe.src = getUrl(host_info.HTTP_REMOTE_ORIGIN, - host_info.HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, true); + iframe.src = getUrl(HTTP_REMOTE_ORIGIN, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT, true); document.body.appendChild(iframe); const tokenPromise = getToken(); await (new Promise((resolve) => iframe.addEventListener('load', resolve))); @@ -86,6 +92,15 @@ promise_test(async (test) => { checkToken('cross-origin redirect with preflight', token); }, 'cross-origin redirect with preflight; setting up'); +promise_test(async (test) => { + const w = window.open( + `${HTTP_NOTSAMESITE_ORIGIN}/fetch/api/resources/keepalive-window.html`); + const tokenPromise = getToken(); + const token = await tokenPromise; + w.close(); + + checkToken('keepalive in onunload in nested frame in another window', token); +}, 'keepalive in onunload in nested frame in another window; setting up'); </script> </body> </html> diff --git a/tests/wpt/web-platform-tests/fetch/api/cors/resources/not-cors-safelisted.json b/tests/wpt/web-platform-tests/fetch/api/cors/resources/not-cors-safelisted.json index 20a162f92c1..945dc0f93ba 100644 --- a/tests/wpt/web-platform-tests/fetch/api/cors/resources/not-cors-safelisted.json +++ b/tests/wpt/web-platform-tests/fetch/api/cors/resources/not-cors-safelisted.json @@ -3,9 +3,11 @@ ["accept", "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678"], ["accept-language", "\u0001"], ["accept-language", "@"], + ["authorization", "basics"], ["content-language", "\u0001"], ["content-language", "@"], ["content-type", "text/html"], ["content-type", "text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901"], + ["range", "bytes 0-"], ["test", "hi"] ] diff --git a/tests/wpt/web-platform-tests/fetch/api/credentials/authentication-basic.any.js b/tests/wpt/web-platform-tests/fetch/api/credentials/authentication-basic.any.js index babc5d003b1..31ccc386977 100644 --- a/tests/wpt/web-platform-tests/fetch/api/credentials/authentication-basic.any.js +++ b/tests/wpt/web-platform-tests/fetch/api/credentials/authentication-basic.any.js @@ -1,11 +1,10 @@ // META: global=window,worker -// META: script=../resources/utils.js function basicAuth(desc, user, pass, mode, status) { promise_test(function(test) { var headers = { "Authorization": "Basic " + btoa(user + ":" + pass)}; var requestInit = {"credentials": mode, "headers": headers}; - return fetch(RESOURCES_DIR + "authentication.py?realm=test", requestInit).then(function(resp) { + return fetch("../resources/authentication.py?realm=test", requestInit).then(function(resp) { assert_equals(resp.status, status, "HTTP status is " + status); assert_equals(resp.type , "basic", "Response's type is basic"); }); @@ -15,3 +14,4 @@ function basicAuth(desc, user, pass, mode, status) { basicAuth("User-added Authorization header with include mode", "user", "password", "include", 200); basicAuth("User-added Authorization header with same-origin mode", "user", "password", "same-origin", 200); basicAuth("User-added Authorization header with omit mode", "user", "password", "omit", 200); +basicAuth("User-added bogus Authorization header with omit mode", "notuser", "notpassword", "omit", 401); diff --git a/tests/wpt/web-platform-tests/fetch/api/resources/keepalive-window.html b/tests/wpt/web-platform-tests/fetch/api/resources/keepalive-window.html new file mode 100644 index 00000000000..6ccf484644c --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/api/resources/keepalive-window.html @@ -0,0 +1,34 @@ +<!doctype html> +<html> +<meta charset="utf-8"> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script> +const TOKEN = token(); +const { + HTTP_NOTSAMESITE_ORIGIN, + HTTP_REMOTE_ORIGIN, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT +} = get_host_info(); +const REDIRECT_DESTINATION = + `${HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT}/fetch/api/resources/stash-put.py` + + `?key=${TOKEN}&value=on`; +const URL = + `${HTTP_REMOTE_ORIGIN}/fetch/api/resources/redirect.py?` + + `delay=500&` + + `allow_headers=foo&` + + `location=${encodeURIComponent(REDIRECT_DESTINATION)}`; + +addEventListener('load', () => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.contentWindow.addEventListener('unload', () => { + iframe.contentWindow.fetch(URL, {keepalive: true, headers: {foo: 'bar'}}); + }); + + window.opener.postMessage(TOKEN, '*'); + // Do NOT remove `iframe` here. We want to check the case where the nested + // frame is implicitly closed by window closure. +}); +</script> +</html> diff --git a/tests/wpt/web-platform-tests/fetch/api/resources/utils.js b/tests/wpt/web-platform-tests/fetch/api/resources/utils.js index 213d01a8bbf..dfd5c1404cb 100644 --- a/tests/wpt/web-platform-tests/fetch/api/resources/utils.js +++ b/tests/wpt/web-platform-tests/fetch/api/resources/utils.js @@ -1,12 +1,5 @@ -var inWorker = false; var RESOURCES_DIR = "../resources/"; -try { - inWorker = !(self instanceof Window); -} catch (e) { - inWorker = true; -} - function dirname(path) { return path.replace(/\/[^\/]*$/, '/') } diff --git a/tests/wpt/web-platform-tests/fetch/h1-parsing/README.md b/tests/wpt/web-platform-tests/fetch/h1-parsing/README.md new file mode 100644 index 00000000000..487a892dcff --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/h1-parsing/README.md @@ -0,0 +1,5 @@ +This directory tries to document "rough consensus" on where HTTP/1 parsing should end up between browsers. + +Any tests that browsers currently fail should have associated bug reports. + +[whatwg/fetch issue #1156](https://github.com/whatwg/fetch/issues/1156) provides context for this effort and pointers to the various issues, pull requests, and bug reports that are associated with it. diff --git a/tests/wpt/web-platform-tests/fetch/h1-parsing/lone-cr.window.js b/tests/wpt/web-platform-tests/fetch/h1-parsing/lone-cr.window.js new file mode 100644 index 00000000000..6b46ed632f4 --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/h1-parsing/lone-cr.window.js @@ -0,0 +1,23 @@ +// These tests expect that a network error is returned if there's a CR that is not immediately +// followed by LF before reaching message-body. +// +// No browser does this currently, but Firefox does treat it equivalently to a space which gives +// hope. + +[ + "HTTP/1.1\r200 OK\n\nBODY", + "HTTP/1.1 200\rOK\n\nBODY", + "HTTP/1.1 200 OK\n\rHeader: Value\n\nBODY", + "HTTP/1.1 200 OK\nHeader\r: Value\n\nBODY", + "HTTP/1.1 200 OK\nHeader:\r Value\n\nBODY", + "HTTP/1.1 200 OK\nHeader: Value\r\n\nBody", + "HTTP/1.1 200 OK\nHeader: Value\r\r\nBODY", + "HTTP/1.1 200 OK\nHeader: Value\rHeader2: Value2\n\nBODY", + "HTTP/1.1 200 OK\nHeader: Value\n\rBODY", + "HTTP/1.1 200 OK\nHeader: Value\n\r" +].forEach(input => { + promise_test(t => { + const message = encodeURIComponent(input); + return promise_rejects_js(t, TypeError, fetch(`resources/message.py?message=${message}`)); + }, `Parsing response with a lone CR before message-body (${input})`); +}); diff --git a/tests/wpt/web-platform-tests/fetch/h1-parsing/resources/message.py b/tests/wpt/web-platform-tests/fetch/h1-parsing/resources/message.py new file mode 100644 index 00000000000..640080c18bc --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/h1-parsing/resources/message.py @@ -0,0 +1,3 @@ +def main(request, response): + response.writer.write(request.GET.first(b"message")) + response.close_connection = True diff --git a/tests/wpt/web-platform-tests/fetch/redirects/data.window.js b/tests/wpt/web-platform-tests/fetch/redirects/data.window.js new file mode 100644 index 00000000000..eeb41966b44 --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/redirects/data.window.js @@ -0,0 +1,25 @@ +// See ../api/redirect/redirect-to-dataurl.any.js for fetch() tests + +async_test(t => { + const img = document.createElement("img"); + img.onload = t.unreached_func(); + img.onerror = t.step_func_done(); + img.src = "../api/resources/redirect.py?location=data:image/png%3Bbase64,iVBORw0KGgoAAAANSUhEUgAAAIUAAABqCAIAAAAdqgU8AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAF6SURBVHhe7dNBDQAADIPA%2Bje92eBxSQUQSLedlQzo0TLQonFWPVoGWjT%2BoUfLQIvGP/RoGWjR%2BIceLQMtGv/Qo2WgReMferQMtGj8Q4%2BWgRaNf%2BjRMtCi8Q89WgZaNP6hR8tAi8Y/9GgZaNH4hx4tAy0a/9CjZaBF4x96tAy0aPxDj5aBFo1/6NEy0KLxDz1aBlo0/qFHy0CLxj/0aBlo0fiHHi0DLRr/0KNloEXjH3q0DLRo/EOPloEWjX/o0TLQovEPPVoGWjT%2BoUfLQIvGP/RoGWjR%2BIceLQMtGv/Qo2WgReMferQMtGj8Q4%2BWgRaNf%2BjRMtCi8Q89WgZaNP6hR8tAi8Y/9GgZaNH4hx4tAy0a/9CjZaBF4x96tAy0aPxDj5aBFo1/6NEy0KLxDz1aBlo0/qFHy0CLxj/0aBlo0fiHHi0DLRr/0KNloEXjH3q0DLRo/EOPloEWjX/o0TLQovEPPVoGWjT%2BoUfLQIvGP/RoGWjR%2BIceJQMPIOzeGc0PIDEAAAAASUVORK5CYII"; +}, "<img> fetch that redirects to data: URL"); + +globalThis.globalTest = null; +async_test(t => { + globalThis.globalTest = t; + const script = document.createElement("script"); + script.src = "../api/resources/redirect.py?location=data:text/javascript,(globalThis.globalTest.unreached_func())()"; + script.onerror = t.step_func_done(); + document.body.append(script); +}, "<script> fetch that redirects to data: URL"); + +async_test(t => { + const client = new XMLHttpRequest(); + client.open("GET", "../api/resources/redirect.py?location=data:,"); + client.send(); + client.onload = t.unreached_func(); + client.onerror = t.step_func_done(); +}, "XMLHttpRequest fetch that redirects to data: URL"); diff --git a/tests/wpt/web-platform-tests/fetch/redirects/subresource-fragments.html b/tests/wpt/web-platform-tests/fetch/redirects/subresource-fragments.html new file mode 100644 index 00000000000..61f91b6a369 --- /dev/null +++ b/tests/wpt/web-platform-tests/fetch/redirects/subresource-fragments.html @@ -0,0 +1,39 @@ +<!doctype html> +<meta charset=utf-8> +<title>Subresources and fragment preservation</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/html/canvas/resources/canvas-tests.js></script> +<div id=log></div> +<!-- + The source image is 50h x 100w and its color depends on the fragment. + + This image is then drawn on a 50h x 100w transparent black canvas. +--> +<img data-desc="Control" + src="/images/colors.svg#green"> +<img data-desc="Redirect with the original URL containing a fragment" + src="../api/resources/redirect.py?simple&location=/images/colors.svg#green"> +<img data-desc="Redirect with the response Location header containing a fragment" + src="../api/resources/redirect.py?simple&location=/images/colors.svg%23green"> +<img data-desc="Redirect with both the original URL and response Location header containing a fragment" + src="../api/resources/redirect.py?simple&location=/images/colors.svg%23green#red"> +<canvas width=100 height=50></canvas> +<script> +setup({ explicit_done:true }); +onload = () => { + const canvas = document.querySelector("canvas"); + const ctx = canvas.getContext("2d"); + document.querySelectorAll("img").forEach(img => { + test(t => { + t.add_cleanup(() => { + ctx.clearRect(0, 0, 100, 50); + }); + ctx.drawImage(img, 0, 0); + // canvas, pixelX, pixelY, r, g, b, alpha, ?, ?, tolerance + _assertPixelApprox(canvas, 40, 40, 0, 255, 0, 255, undefined, undefined, 4); + }, img.dataset.desc); + }); + done(); +}; +</script> diff --git a/tests/wpt/web-platform-tests/file-system-access/script-tests/FileSystemWritableFileStream-write.js b/tests/wpt/web-platform-tests/file-system-access/script-tests/FileSystemWritableFileStream-write.js index 141a9771d2a..ea4915a1410 100644 --- a/tests/wpt/web-platform-tests/file-system-access/script-tests/FileSystemWritableFileStream-write.js +++ b/tests/wpt/web-platform-tests/file-system-access/script-tests/FileSystemWritableFileStream-write.js @@ -117,14 +117,12 @@ directory_test(async (t, root) => { const handle = await createEmptyFile(t, 'bad_offset', root); const stream = await handle.createWritable(); - await promise_rejects_dom( - t, 'InvalidStateError', stream.write({type: 'write', position: 4, data: new Blob(['abc'])})); - await promise_rejects_js( - t, TypeError, stream.close(), 'stream is already closed'); + await stream.write({type: 'write', position: 4, data: new Blob(['abc'])}); + await stream.close(); - assert_equals(await getFileContents(handle), ''); - assert_equals(await getFileSize(handle), 0); -}, 'write() called with an invalid offset'); + assert_equals(await getFileContents(handle), '\0\0\0\0abc'); + assert_equals(await getFileSize(handle), 7); +}, 'write() called with an offset beyond the end of the file'); directory_test(async (t, root) => { const handle = await createEmptyFile(t, 'empty_string', root); diff --git a/tests/wpt/web-platform-tests/file-system-access/showPicker-errors.https.window.js b/tests/wpt/web-platform-tests/file-system-access/showPicker-errors.https.window.js index 2310c323d9f..189f47344ca 100644 --- a/tests/wpt/web-platform-tests/file-system-access/showPicker-errors.https.window.js +++ b/tests/wpt/web-platform-tests/file-system-access/showPicker-errors.https.window.js @@ -86,6 +86,18 @@ function define_file_picker_error_tests(showPickerMethod) { })); }, showPickerMethod + ': unknown well-known starting directory.'); + promise_test(async t => { + await promise_rejects_js(t, TypeError, self[showPickerMethod]({ + id: "inv*l:d\\ chara<ters", + })); + }, showPickerMethod + ': starting directory ID contains invalid characters.'); + + promise_test(async t => { + await promise_rejects_js(t, TypeError, self[showPickerMethod]({ + id: "id-length-cannot-exceed-32-characters", + })); + }, showPickerMethod + ': starting directory ID cannot exceed 32 characters.'); + const invalid_extensions = { '.extensiontoolong': 'extension length more than 16.', '.txt.': 'extenstion ends with "."', diff --git a/tests/wpt/web-platform-tests/html/browsers/sandboxing/resources/204-no-content.asis b/tests/wpt/web-platform-tests/html/browsers/sandboxing/resources/204-no-content.asis deleted file mode 100644 index 58e46abbc9e..00000000000 --- a/tests/wpt/web-platform-tests/html/browsers/sandboxing/resources/204-no-content.asis +++ /dev/null @@ -1 +0,0 @@ -HTTP/1.1 204 No Content diff --git a/tests/wpt/web-platform-tests/html/browsers/sandboxing/window-open-blank-from-different-initiator.html b/tests/wpt/web-platform-tests/html/browsers/sandboxing/window-open-blank-from-different-initiator.html index aa269fdf14c..28345d2a8e8 100644 --- a/tests/wpt/web-platform-tests/html/browsers/sandboxing/window-open-blank-from-different-initiator.html +++ b/tests/wpt/web-platform-tests/html/browsers/sandboxing/window-open-blank-from-different-initiator.html @@ -77,8 +77,7 @@ runTest("One pending navigation", async (test, popup_name) => { // is canceled. As a result, the frame will be left with the initial empty // document and NO pending navigation. runTest("No pending navigation", async (test, popup_name) => { - const no_content_path = - "/html/browsers/sandboxing/resources/204-no-content.asis"; + const no_content_path = "/common/blank.html?pipe=status(204)" const popup = window.open(same_origin + no_content_path, popup_name); // Unfortunately, there are no web API to detect a navigation has been diff --git a/tests/wpt/web-platform-tests/html/canvas/element/manual/imagebitmap/createImageBitmap-bounds.html b/tests/wpt/web-platform-tests/html/canvas/element/manual/imagebitmap/createImageBitmap-bounds.html index d179f080bf5..9c4f1401e2d 100644 --- a/tests/wpt/web-platform-tests/html/canvas/element/manual/imagebitmap/createImageBitmap-bounds.html +++ b/tests/wpt/web-platform-tests/html/canvas/element/manual/imagebitmap/createImageBitmap-bounds.html @@ -4,41 +4,64 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/html/canvas/resources/canvas-tests.js"></script> +<div id="results"></div> <script> -promise_test(function(t) { - return new Promise(function(resolve, reject) { - const image = new Image(); - image.onload = function() { resolve(image); }; - image.onerror = function() { reject(); }; - image.src = "/images/green-16x16.png"; - }).then(function(image) { - return createImageBitmap(image, 8, 8, 16, 16); - }).then(function(imageBitmap) { - const color = 204; +const color = 204; +function testClip( name, sx, sy, sw, sh, expectedColors, expectedWidth, expectedHeight ) { + promise_test(function(t) { + return new Promise(function(resolve, reject) { + const image = new Image(); + image.onload = function() { resolve(image); }; + image.onerror = function() { reject(); }; + image.src = "/images/green-16x16.png"; + }).then(function(image) { + return createImageBitmap(image, sx, sy, sw, sh); + }).then(function(imageBitmap) { - const canvas = document.createElement("canvas"); - canvas.width = 16; - canvas.height = 16; + assert_equals(imageBitmap.width, expectedWidth); + assert_equals(imageBitmap.height, expectedHeight); - // debug - document.body.appendChild(canvas); - canvas.setAttribute("style", "width: 100px; height: 100px;"); + const canvas = document.createElement("canvas"); + canvas.width = 16; + canvas.height = 16; - const ctx = canvas.getContext("2d"); - ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; - ctx.fillRect(0, 0, 20, 20); - ctx.drawImage(imageBitmap, 0, 0); + // debug + document.getElementById("results").append(canvas); + canvas.setAttribute("style", "width: 100px; height: 100px;"); - const expected = [ - [ 4, 4, 0,255,0,255], - [12, 4, color,color,color,255], - [ 4, 12, color,color,color,255], - [12, 12, color,color,color,255], - ]; - for (let [x, y, r, g, b, a] of expected) { - _assertPixel(canvas, x,y, r,g,b,a, `${x},${y}`, `${r},${g},${b},${a}`); - } + const ctx = canvas.getContext("2d"); + ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; + ctx.fillRect(0, 0, 20, 20); + ctx.drawImage(imageBitmap, 0, 0); - }); -}); + for (let [x, y, r, g, b, a] of expectedColors) { + _assertPixel(canvas, x,y, r,g,b,a, `${x},${y}`, `${r},${g},${b},${a}`); + } + }); + }, name); +} +testClip( "simple clip inside", + 8, 8, 8, 8, [ + [ 4, 4, 0,255,0,255], [12, 4, color,color,color,255], + [ 4, 12, color,color,color,255], [12, 12, color,color,color,255] + ], 8, 8 +); +testClip( "clip outside negative", + -8, -8, 16, 16, [ + [ 4, 4, color,color,color,255], [12, 4, color,color,color,255], + [ 4, 12, color,color,color,255], [12, 12, 0,255,0,255] + ], 16, 16 +); +testClip( "clip outside positive", + 8, 8, 16, 16, [ + [ 4, 4, 0,255,0,255], [12, 4, color,color,color,255], + [ 4, 12, color,color,color,255], [12, 12, color,color,color,255] + ], 16, 16 +); +testClip( "clip inside using negative width and height", + 24, 24, -16, -16, [ + [ 4, 4, 0,255,0,255], [12, 4, color,color,color,255], + [ 4, 12, color,color,color,255], [12, 12, color,color,color,255] + ], 16, 16 +); </script> diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.perspective.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.perspective.html new file mode 100644 index 00000000000..6aa4a224d4b --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.perspective.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.perspective</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.perspective</h1> +<p class="desc">perspective() results in the correct transformation matrix</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("perspective() results in the correct transformation matrix"); +_addTest(function(canvas, ctx) { + +const length = 100; +ctx.perspective(length); +const domMatrix = new DOMMatrix(); +domMatrix.m34 = -1/length; +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()); +ctx.rotateAxis(1, 2, 3, 4); +domMatrix.rotateAxisAngleSelf(1, 2, 3, rad2deg(4)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.X.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.X.html new file mode 100644 index 00000000000..42a4e3c45a0 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.X.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.rotate3d.X</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.rotate3d.X</h1> +<p class="desc">rotate3d() around the x axis results in the correct transformation matrix</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("rotate3d() around the x axis results in the correct transformation matrix"); +_addTest(function(canvas, ctx) { + +// angles are in radians, test something that is not a multiple of pi +const angle = 2; +const domMatrix = new DOMMatrix(); +ctx.rotate3d(angle, 0, 0); +domMatrix.rotateAxisAngleSelf(1, 0, 0, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()) +ctx.rotate3d(angle, 0, 0); +domMatrix.rotateAxisAngleSelf(1, 0, 0, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.Y.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.Y.html new file mode 100644 index 00000000000..5006769fa40 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.Y.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.rotate3d.Y</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.rotate3d.Y</h1> +<p class="desc">rotate3d() around the y axis results in the correct transformation matrix</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("rotate3d() around the y axis results in the correct transformation matrix"); +_addTest(function(canvas, ctx) { + +// angles are in radians, test something that is not a multiple of pi +const angle = 2; +const domMatrix = new DOMMatrix(); +ctx.rotate3d(0, angle, 0); +domMatrix.rotateAxisAngleSelf(0, 1, 0, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()) +ctx.rotate3d(0, angle, 0); +domMatrix.rotateAxisAngleSelf(0, 1, 0, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.Z.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.Z.html new file mode 100644 index 00000000000..71e113dfe5b --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.Z.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.rotate3d.Z</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.rotate3d.Z</h1> +<p class="desc">rotate3d() around the z axis results in the correct transformation matrix</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("rotate3d() around the z axis results in the correct transformation matrix"); +_addTest(function(canvas, ctx) { + +// angles are in radians, test something that is not a multiple of pi +const angle = 2; +const domMatrix = new DOMMatrix(); +ctx.rotate3d(0, 0, angle); +domMatrix.rotateAxisAngleSelf(0, 0, 1, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()) +ctx.rotate3d(0, 0, angle); +domMatrix.rotateAxisAngleSelf(0, 0, 1, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.html new file mode 100644 index 00000000000..104e0870f27 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotate3d.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.rotate3d</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.rotate3d</h1> +<p class="desc">rotate3d() results in the correct transformation matrix</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("rotate3d() results in the correct transformation matrix"); +_addTest(function(canvas, ctx) { + +// angles are in radians, test something that is not a multiple of pi +const angleX = 2; +const angleY = 3; +const angleZ = 4; +const domMatrix = new DOMMatrix(); +ctx.rotate3d(angleX, angleY, angleZ); +domMatrix.rotateSelf(rad2deg(angleX), rad2deg(angleY), rad2deg(angleZ)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()); +ctx.rotate3d(angleX, angleY, angleZ); +domMatrix.rotateSelf(rad2deg(angleX), rad2deg(angleY), rad2deg(angleZ)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotateAxis.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotateAxis.html new file mode 100644 index 00000000000..be0785a7d36 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.rotateAxis.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.rotateAxis</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.rotateAxis</h1> +<p class="desc">rotateAxis() results in the correct transformation matrix</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("rotateAxis() results in the correct transformation matrix"); +_addTest(function(canvas, ctx) { + +// angles are in radians, test something that is not a multiple of pi +const angle = 2; +const axis = {x: 1, y: 2, z: 3} +const domMatrix = new DOMMatrix(); +ctx.rotateAxis(axis.x, axis.y, axis.z, angle); +domMatrix.rotateAxisAngleSelf(axis.x, axis.y, axis.z, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()); +ctx.rotateAxis(axis.x, axis.y, axis.z, angle); +domMatrix.rotateAxisAngleSelf(axis.x, axis.y, axis.z, rad2deg(angle)); +_assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.scale.3d.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.scale.3d.html new file mode 100644 index 00000000000..0231b3bfba8 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.scale.3d.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.scale.3d</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.scale.3d</h1> +<p class="desc">scale() function with three arguments modifies the underlying matrix appropriately</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("scale() function with three arguments modifies the underlying matrix appropriately"); +_addTest(function(canvas, ctx) { + +const sx = 2; +const sy = 3; +const sz = 4; +ctx.scale(sx, sy, sz); +let canvasTransform = ctx.getTransform(); +_assert(canvasTransform.m11 = sx, "canvasTransform.m11 = sx"); +_assert(canvasTransform.m22 = sy, "canvasTransform.m22 = sy"); +_assert(canvasTransform.m33 = sz, "canvasTransform.m33 = sz"); +ctx.scale(sx, sy, sz); +canvasTransform = ctx.getTransform(); +_assert(canvasTransform.m11 = 2 * sx, "canvasTransform.m11 = 2 * sx"); +_assert(canvasTransform.m22 = 2 * sy, "canvasTransform.m22 = 2 * sy"); +_assert(canvasTransform.m33 = 2 * sz, "canvasTransform.m33 = 2 * sz"); + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.translate.3d.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.translate.3d.html new file mode 100644 index 00000000000..f3c68ce0506 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.translate.3d.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> +<title>Canvas test: 2d.transformation.translate.3d</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/html/canvas/resources/canvas-tests.js"></script> +<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> +<body class="show_output"> + +<h1>2d.transformation.translate.3d</h1> +<p class="desc">translate() function with three arguments modifies the underlying matrix appropriately</p> + + +<p class="output">Actual output:</p> +<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> + +<ul id="d"></ul> +<script> +var t = async_test("translate() function with three arguments modifies the underlying matrix appropriately"); +_addTest(function(canvas, ctx) { + +const dx = 2; +const dy = 3; +const dz = 4; +ctx.translate(dx, dy, dz); +let canvasTransform = ctx.getTransform(); +_assert(canvasTransform.m41 = dx, "canvasTransform.m41 = dx"); +_assert(canvasTransform.m42 = dy, "canvasTransform.m42 = dy"); +_assert(canvasTransform.m43 = dz, "canvasTransform.m43 = dz"); +ctx.translate(dx, dy, dz); +canvasTransform = ctx.getTransform(); +_assert(canvasTransform.m41 = 2 * dx, "canvasTransform.m41 = 2 * dx"); +_assert(canvasTransform.m42 = 2 * dy, "canvasTransform.m42 = 2 * dy"); +_assert(canvasTransform.m43 = 2 * dz, "canvasTransform.m43 = 2 * dz"); + + +}); +</script> + diff --git a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.translate3d.html b/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.translate3d.html deleted file mode 100644 index 2fee078bb3c..00000000000 --- a/tests/wpt/web-platform-tests/html/canvas/element/transformations/2d.transformation.translate3d.html +++ /dev/null @@ -1,39 +0,0 @@ -<!DOCTYPE html> -<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. --> -<title>Canvas test: 2d.transformation.translate3d</title> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="/html/canvas/resources/canvas-tests.js"></script> -<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css"> -<body class="show_output"> - -<h1>2d.transformation.translate3d</h1> -<p class="desc">translate3d() function modifies the underlying matrix appropriately</p> - - -<p class="output">Actual output:</p> -<canvas id="c" class="output" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas> - -<ul id="d"></ul> -<script> -var t = async_test("translate3d() function modifies the underlying matrix appropriately"); -_addTest(function(canvas, ctx) { - -const dx = 2; -const dy = 3; -const dz = 4; -ctx.translate3d(dx, dy, dz); -let canvasTransform = ctx.getTransform(); -_assert(canvasTransform.m41 = dx, "canvasTransform.m41 = dx"); -_assert(canvasTransform.m42 = dy, "canvasTransform.m42 = dy"); -_assert(canvasTransform.m43 = dz, "canvasTransform.m43 = dz"); -ctx.translate3d(dx, dy, dz); -canvasTransform = ctx.getTransform(); -_assert(canvasTransform.m41 = 2 * dx, "canvasTransform.m41 = 2 * dx"); -_assert(canvasTransform.m42 = 2 * dy, "canvasTransform.m42 = 2 * dy"); -_assert(canvasTransform.m43 = 2 * dz, "canvasTransform.m43 = 2 * dz"); - - -}); -</script> - diff --git a/tests/wpt/web-platform-tests/html/canvas/resources/canvas-tests.js b/tests/wpt/web-platform-tests/html/canvas/resources/canvas-tests.js index 0ccb475b763..7d09dcfde75 100644 --- a/tests/wpt/web-platform-tests/html/canvas/resources/canvas-tests.js +++ b/tests/wpt/web-platform-tests/html/canvas/resources/canvas-tests.js @@ -50,6 +50,24 @@ function _assertPixelApprox(canvas, x,y, r,g,b,a, pos, colour, tolerance) assert_approx_equals(c[3], a, tolerance, 'Alpha channel of the pixel at (' + x + ', ' + y + ')'); } +function _assertMatricesApproxEqual(matA, matB) +{ + A = matA.toFloat32Array(); + B = matB.toFloat32Array(); + assert_equals(A.length, B.length); + for (var i = 0; i < A.length; i++) { + assert_approx_equals(A[i], B[i], 10e-6); + } +} + +function rad2deg(angle_in_radians) { + return angle_in_radians / Math.PI * 180; +} + +function deg2rad(angle_in_degrees) { + return angle_in_degrees / 180 * Math.PI; +} + let _deferred = false; function deferTest() { diff --git a/tests/wpt/web-platform-tests/html/canvas/tools/yaml/element/transformations.yaml b/tests/wpt/web-platform-tests/html/canvas/tools/yaml/element/transformations.yaml index add421e4eb5..65346ca1f80 100644 --- a/tests/wpt/web-platform-tests/html/canvas/tools/yaml/element/transformations.yaml +++ b/tests/wpt/web-platform-tests/html/canvas/tools/yaml/element/transformations.yaml @@ -425,21 +425,132 @@ transform.multiplySelf(transform); @assert transform.toLocaleString() == canvasTransform.toLocaleString(); -- name: 2d.transformation.translate3d - desc: translate3d() function modifies the underlying matrix appropriately +- name: 2d.transformation.translate.3d + desc: translate() function with three arguments modifies the underlying matrix appropriately testing: - - 2d.transformation.translate3d + - 2d.transformation.translate.3d code: | const dx = 2; const dy = 3; const dz = 4; - ctx.translate3d(dx, dy, dz); + ctx.translate(dx, dy, dz); let canvasTransform = ctx.getTransform(); @assert canvasTransform.m41 = dx; @assert canvasTransform.m42 = dy; @assert canvasTransform.m43 = dz; - ctx.translate3d(dx, dy, dz); + ctx.translate(dx, dy, dz); canvasTransform = ctx.getTransform(); @assert canvasTransform.m41 = 2 * dx; @assert canvasTransform.m42 = 2 * dy; @assert canvasTransform.m43 = 2 * dz; + +- name: 2d.transformation.scale.3d + desc: scale() function with three arguments modifies the underlying matrix appropriately + testing: + - 2d.transformation.scale.3d + code: | + const sx = 2; + const sy = 3; + const sz = 4; + ctx.scale(sx, sy, sz); + let canvasTransform = ctx.getTransform(); + @assert canvasTransform.m11 = sx; + @assert canvasTransform.m22 = sy; + @assert canvasTransform.m33 = sz; + ctx.scale(sx, sy, sz); + canvasTransform = ctx.getTransform(); + @assert canvasTransform.m11 = 2 * sx; + @assert canvasTransform.m22 = 2 * sy; + @assert canvasTransform.m33 = 2 * sz; + +- name: 2d.transformation.rotate3d.X + desc: rotate3d() around the x axis results in the correct transformation matrix + testing: + - 2d.transformation.rotate3d.X + code: | + // angles are in radians, test something that is not a multiple of pi + const angle = 2; + const domMatrix = new DOMMatrix(); + ctx.rotate3d(angle, 0, 0); + domMatrix.rotateAxisAngleSelf(1, 0, 0, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + ctx.rotate3d(angle, 0, 0); + domMatrix.rotateAxisAngleSelf(1, 0, 0, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + +- name: 2d.transformation.rotate3d.Y + desc: rotate3d() around the y axis results in the correct transformation matrix + testing: + - 2d.transformation.rotate3d.Y + code: | + // angles are in radians, test something that is not a multiple of pi + const angle = 2; + const domMatrix = new DOMMatrix(); + ctx.rotate3d(0, angle, 0); + domMatrix.rotateAxisAngleSelf(0, 1, 0, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + ctx.rotate3d(0, angle, 0); + domMatrix.rotateAxisAngleSelf(0, 1, 0, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + +- name: 2d.transformation.rotate3d.Z + desc: rotate3d() around the z axis results in the correct transformation matrix + testing: + - 2d.transformation.rotate3d.Z + code: | + // angles are in radians, test something that is not a multiple of pi + const angle = 2; + const domMatrix = new DOMMatrix(); + ctx.rotate3d(0, 0, angle); + domMatrix.rotateAxisAngleSelf(0, 0, 1, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + ctx.rotate3d(0, 0, angle); + domMatrix.rotateAxisAngleSelf(0, 0, 1, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()) + +- name: 2d.transformation.rotate3d + desc: rotate3d() results in the correct transformation matrix + testing: + - 2d.transformation.rotate3d + code: | + // angles are in radians, test something that is not a multiple of pi + const angleX = 2; + const angleY = 3; + const angleZ = 4; + const domMatrix = new DOMMatrix(); + ctx.rotate3d(angleX, angleY, angleZ); + domMatrix.rotateSelf(rad2deg(angleX), rad2deg(angleY), rad2deg(angleZ)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + ctx.rotate3d(angleX, angleY, angleZ); + domMatrix.rotateSelf(rad2deg(angleX), rad2deg(angleY), rad2deg(angleZ)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + +- name: 2d.transformation.rotateAxis + desc: rotateAxis() results in the correct transformation matrix + testing: + - 2d.transformation.rotateAxis + code: | + // angles are in radians, test something that is not a multiple of pi + const angle = 2; + const axis = {x: 1, y: 2, z: 3} + const domMatrix = new DOMMatrix(); + ctx.rotateAxis(axis.x, axis.y, axis.z, angle); + domMatrix.rotateAxisAngleSelf(axis.x, axis.y, axis.z, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + ctx.rotateAxis(axis.x, axis.y, axis.z, angle); + domMatrix.rotateAxisAngleSelf(axis.x, axis.y, axis.z, rad2deg(angle)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + +- name: 2d.transformation.perspective + desc: perspective() results in the correct transformation matrix + testing: + - 2d.transformation.perspective + code: | + const length = 100; + ctx.perspective(length); + const domMatrix = new DOMMatrix(); + domMatrix.m34 = -1/length; + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()); + ctx.rotateAxis(1, 2, 3, 4); + domMatrix.rotateAxisAngleSelf(1, 2, 3, rad2deg(4)); + _assertMatricesApproxEqual(domMatrix, ctx.getTransform()); diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/META.yml b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/META.yml new file mode 100644 index 00000000000..2bf6754a6bb --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/META.yml @@ -0,0 +1,7 @@ +spec: To be defined. +suggested_reviewers: + - annevk + - arthursonzogni + - arturjanc + - camillelamy + - mikewest diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/README.md b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/README.md new file mode 100644 index 00000000000..57de8e53533 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/README.md @@ -0,0 +1,15 @@ +# Helper files: + +- `resources/dispatcher.js` provides `send()` and `receive()`. This is an + universal message passing API. It works cross-origin, and even access + different browser context groups. Messages are queued, this means one doesn't + need to wait for the receiver to listen, before sending the first message. + +- `resources/executor.html` is a document. Test can send arbitrary javascript to evaluate + in its execution context. This is universal and avoids introducing many + specific `XXX-helper.html` resources. Moreover, tests are easier to read, + because the whole logic of the test can be defined in a single file. + +# Related documents: +- https://github.com/mikewest/credentiallessness/ +- https://github.com/w3ctag/design-reviews/issues/582 diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/fetch.tentative.https.html b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/fetch.tentative.https.html new file mode 100644 index 00000000000..098260119ab --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/fetch.tentative.https.html @@ -0,0 +1,145 @@ +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="./resources/common.js"></script> +<script src="./resources/dispatcher.js"></script> +<script> + +promise_test(async test => { + const same_origin = get_host_info().HTTPS_ORIGIN; + const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const cookie_key = "coep_credentialless_fetch"; + const cookie_same_origin = "same_origin"; + const cookie_cross_origin = "cross_origin"; + + // Set cookie on the same_origin. + document.cookie = `${cookie_key}=${cookie_same_origin}`; + + // Set cookie on a different origin. + { + const w_token = token(); + const w_url = cross_origin + executor_path + `&uuid=${w_token}`; + const w = window.open(w_url); + + const reply_token = token(); + send(w_token, ` + document.cookie = "${cookie_key}=${cookie_cross_origin}"; + send("${reply_token}", "done"); + `); + assert_equals(await receive(reply_token), "done"); + w.close(); + } + + // One window with COEP:none. (control) + const w_control_token = token(); + const w_control_url = same_origin + executor_path + + coep_none + `&uuid=${w_control_token}` + const w_control = window.open(w_control_url); + + // One window with COEP:credentialless. (experiment) + const w_credentialless_token = token(); + const w_credentialless_url = same_origin + executor_path + + coep_credentialless + `&uuid=${w_credentialless_token}`; + const w_credentialless = window.open(w_credentialless_url); + + const fetchTest = function( + description, origin, mode, credentials, + expected_cookies_control, + expected_cookies_credentialless) + { + promise_test_parallel(async test => { + const token_1 = token(); + const token_2 = token(); + + send(w_control_token, ` + fetch("${showRequestHeaders(origin, token_1)}", { + mode:"${mode}", + credentials: "${credentials}", + }); + `); + send(w_credentialless_token, ` + fetch("${showRequestHeaders(origin, token_2)}", { + mode:"${mode}", + credentials: "${credentials}", + }); + `); + + const headers_control = JSON.parse(await receive(token_1)); + const headers_credentialless = JSON.parse(await receive(token_2)); + + assert_equals(parseCookies(headers_control)[cookie_key], + expected_cookies_control, + "coep:none => "); + assert_equals(parseCookies(headers_credentialless)[cookie_key], + expected_cookies_credentialless, + "coep:credentialless => "); + }, `fetch ${description}`) + }; + + // Cookies are never sent with credentials='omit' + fetchTest("same-origin + no-cors + credentials:omit", + same_origin, 'no-cors', 'omit', + undefined, + undefined); + fetchTest("same-origin + cors + credentials:omit", + same_origin, 'cors', 'omit', + undefined, + undefined); + fetchTest("cross-origin + no-cors + credentials:omit", + cross_origin, 'no-cors', 'omit', + undefined, + undefined); + fetchTest("cross-origin + cors + credentials:omit", + cross_origin, 'cors', 'omit', + undefined, + undefined); + + // Same-origin request contains Cookies. + fetchTest("same-origin + no-cors + credentials:include", + same_origin, 'no-cors', 'include', + cookie_same_origin, + cookie_same_origin); + fetchTest("same-origin + cors + credentials:include", + same_origin, 'cors', 'include', + cookie_same_origin, + cookie_same_origin); + fetchTest("same-origin + no-cors + credentials:same-origin", + same_origin, 'no-cors', 'same-origin', + cookie_same_origin, + cookie_same_origin); + fetchTest("same-origin + cors + credentials:same-origin", + same_origin, 'cors', 'same-origin', + cookie_same_origin, + cookie_same_origin); + + // Cross-origin CORS requests contains Cookies, if credentials mode is set to + // 'include'. This does not depends on COEP. + fetchTest("cross-origin + cors + credentials:include", + cross_origin, 'cors', 'include', + cookie_cross_origin, + cookie_cross_origin); + fetchTest("cross-origin + cors + same-origin-credentials", + cross_origin, 'cors', 'same-origin', + undefined, + undefined); + + // Cross-origin no-CORS requests includes Cookies when: + // 1. credentials mode is 'include' + // 2. COEP: is not credentialless. + fetchTest("cross-origin + no-cors + credentials:include", + cross_origin, 'no-cors', 'include', + cookie_cross_origin, + undefined); + + fetchTest("cross-origin + no-cors + credentials:same-origin", + cross_origin, 'no-cors', 'same-origin', + undefined, + undefined); + + // Cleanup. Safe, because scheduled after every requests from `fetchTest`. + send(w_control_token, `close()`); + send(w_credentialless_token, `close()`); +}, ""); + +</script> diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/common.js b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/common.js new file mode 100644 index 00000000000..be6bb0ea5f6 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/common.js @@ -0,0 +1,30 @@ +const directory = '/html/cross-origin-embedder-policy/credentialless'; + +const executor_path = directory + '/resources/executor.html?pipe='; +const coep_none = '|header(Cross-Origin-Embedder-Policy,none)'; +const coep_credentialless = + '|header(Cross-Origin-Embedder-Policy,credentialless)'; + +// Test using the modern async/await primitives are easier to read/write. +// However they run sequentially, contrary to async_test. This is the parallel +// version, to avoid timing out. +let promise_test_parallel = (promise, description) => { + async_test(test => { + promise(test) + .then(() => test.done()) + .catch(test.step_func(error => { throw error; })); + }, description); +}; + +let parseCookies = function(headers_json) { + if (!headers_json["cookie"]) + return {}; + + return headers_json["cookie"] + .split(';') + .map(v => v.split('=')) + .reduce((acc, v) => { + acc[v[0]] = v[1]; + return acc; + }, {}); +} diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/dispatcher.js b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/dispatcher.js new file mode 100644 index 00000000000..d1fd1b61d98 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/dispatcher.js @@ -0,0 +1,33 @@ +// Define an universal message passing API. It works cross-origin and across +// browsing context groups. +const dispatcher_path = + "/html/cross-origin-embedder-policy/credentialless/resources/dispatcher.py"; +const dispatcher_url = new URL(dispatcher_path, location.href).href; + +const send = async function(uuid, message) { + // The official web-platform-test runner sometimes drop POST requests when + // many are requested in parallel. Using a lock fixes the issue. + await navigator.locks.request("dispatcher_send", async lock => { + await fetch(dispatcher_url + `?uuid=${uuid}`, { + method: 'POST', + body: message + }); + }); +} + +const receive = async function(uuid) { + while(1) { + let response = await fetch(dispatcher_url + `?uuid=${uuid}`); + let data = await response.text(); + if (data != 'not ready') + return data; + await new Promise(r => setTimeout(r, 10 + 100*Math.random())); + } +} + +// Returns an URL. When called, the server sends toward the `uuid` queue the +// request headers. Useful for determining if something was requested with +// Cookies. +const showRequestHeaders= function(origin, uuid) { + return origin + dispatcher_path + `?uuid=${uuid}&show-headers`; +} diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/dispatcher.py b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/dispatcher.py new file mode 100644 index 00000000000..bdce9485f13 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/dispatcher.py @@ -0,0 +1,49 @@ +import json +from wptserve.utils import isomorphic_decode + +# A server used to store and retrieve arbitrary data. +# This is used by: ./dispatcher.js +def main(request, response): + # This server is configured so that is accept to receive any requests and + # any cookies the web browser is willing to send. + response.headers.set(b"Access-Control-Allow-Credentials", b"true") + response.headers.set(b'Access-Control-Allow-Methods', b'OPTIONS, GET, POST') + response.headers.set(b'Access-Control-Allow-Headers', b'Content-Type') + response.headers.set(b'Cache-Control', b'no-cache, no-store, must-revalidate') + response.headers.set(b"Access-Control-Allow-Origin", request.headers.get(b"origin") or '*') + + # CORS preflight + if request.method == u'OPTIONS': + return b'' + + uuid = request.GET[b'uuid'] + stash = request.server.stash; + + # The stash is accessed concurrently by many clients. A lock is used to + # avoid unterleaved read/write from different clients. + with stash.lock: + queue = stash.take(uuid) or []; + + # Push into the |uuid| queue, the requested headers. + if b"show-headers" in request.GET: + headers = {}; + for key, value in request.headers.items(): + headers[isomorphic_decode(key)] = isomorphic_decode(request.headers[key]) + headers = json.dumps(headers); + queue.append(headers); + ret = headers; + + # Push into the |uuid| queue, the posted data. + elif request.method == u'POST': + queue.append(request.body) + ret = b'done' + + # Pull from the |uuid| queue, the posted data. + else: + if len(queue) == 0: + ret = b'not ready' + else: + ret = queue.pop(0) + + stash.put(uuid, queue) + return ret; diff --git a/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/executor.html b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/executor.html new file mode 100644 index 00000000000..7a390a3d7e9 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/cross-origin-embedder-policy/credentialless/resources/executor.html @@ -0,0 +1,15 @@ +<script src="./dispatcher.js"></script> +<script> + + const params = new URLSearchParams(window.location.search); + const uuid = params.get('uuid'); + + let executeOrders = async function() { + while(true) { + let task = await receive(uuid); + eval(`(async () => {${task}})()`); + } + }; + executeOrders(); + +</script> diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-four-reports.https.html.sub.headers b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-four-reports.https.html.sub.headers index 5c886ad0538..50372c17fa2 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-four-reports.https.html.sub.headers +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-four-reports.https.html.sub.headers @@ -3,4 +3,4 @@ Cross-Origin-Opener-Policy-Report-Only: same-origin; report-to="coop-report-only Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Embedder-Policy-Report-Only: require-corp Referrer-Policy: origin -report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} +report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-same-origin-report-to.https.html.sub.headers b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-same-origin-report-to.https.html.sub.headers index 74690a7186f..3811aeabbe7 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-same-origin-report-to.https.html.sub.headers +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/report-only-same-origin-report-to.https.html.sub.headers @@ -1,3 +1,3 @@ Cross-Origin-Opener-Policy-Report-Only: same-origin; report-to="coop-report-only-endpoint" Referrer-Policy: origin -report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} +report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-coop-navigated-popup.https.html.sub.headers b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-coop-navigated-popup.https.html.sub.headers index e28474a4df8..af9f1e77f21 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-coop-navigated-popup.https.html.sub.headers +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-coop-navigated-popup.https.html.sub.headers @@ -1,2 +1,2 @@ Cross-Origin-Opener-Policy: same-origin-allow-popups; report-to="coop-report-endpoint" -report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} +report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-allow-popups-report-to.https.html.sub.headers b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-allow-popups-report-to.https.html.sub.headers index 7e9bb7afcdc..a72aeef0bb4 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-allow-popups-report-to.https.html.sub.headers +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-allow-popups-report-to.https.html.sub.headers @@ -1,3 +1,3 @@ -report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} +report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} Cross-Origin-Opener-Policy: same-origin-allow-popups; report-to="coop-report-endpoint" Referrer-Policy: origin diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-coep-report-to.https.html.sub.headers b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-coep-report-to.https.html.sub.headers index ebc3da0baa1..c3ac29e137d 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-coep-report-to.https.html.sub.headers +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-coep-report-to.https.html.sub.headers @@ -1,4 +1,4 @@ -report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} +report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} Cross-Origin-Opener-Policy: same-origin; report-to="coop-report-endpoint" Cross-Origin-Embedder-Policy: require-corp -Referrer-Policy: origin
\ No newline at end of file +Referrer-Policy: origin diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-report-to.https.html.sub.headers b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-report-to.https.html.sub.headers index 13df6680817..d11c8477904 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-report-to.https.html.sub.headers +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-same-origin-report-to.https.html.sub.headers @@ -1,3 +1,3 @@ -report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} +report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} Cross-Origin-Opener-Policy: same-origin; report-to="coop-report-endpoint" Referrer-Policy: no-referrer diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-unsafe-none-report-to.https.html.sub.headers b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-unsafe-none-report-to.https.html.sub.headers index a0d12c549fa..23213766b30 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-unsafe-none-report-to.https.html.sub.headers +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/navigation-reporting/reporting-popup-unsafe-none-report-to.https.html.sub.headers @@ -1,2 +1,2 @@ -report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} +report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?endpoint=coop-report-only-endpoint" }]} Cross-Origin-Opener-Policy: unsafe-none; report-to="coop-report-endpoint" diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/resources/report.py b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/resources/report.py deleted file mode 100644 index 8e5ec6d1b9b..00000000000 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/resources/report.py +++ /dev/null @@ -1,40 +0,0 @@ -import json, uuid - -from six import PY3 - -from wptserve.utils import isomorphic_decode - -def main(request, response): - response.headers.set(b'Access-Control-Allow-Origin', b'*') - response.headers.set(b'Access-Control-Allow-Methods', b'OPTIONS, GET, POST') - response.headers.set(b'Access-Control-Allow-Headers', b'Content-Type') - response.headers.set(b'Cache-Control', b'no-cache, no-store, must-revalidate'); - if request.method == u'OPTIONS': # CORS preflight - return b'' - - key = 0 - if b'endpoint' in request.GET: - # Use Python version checking here due to the issue reported on uuid5 handling unicode - # type of name argument at https://bugs.python.org/issue34145 - if PY3: - key = uuid.uuid5(uuid.NAMESPACE_OID, isomorphic_decode(request.GET[b'endpoint'])).urn - else: - key = uuid.uuid5(uuid.NAMESPACE_OID, request.GET[b'endpoint']).urn - - if key == 0: - response.status = 400 - return b'invalid endpoint' - - if request.method == u'POST': - reports = request.server.stash.take(key) or [] - for report in json.loads(request.body): - reports.append(report) - request.server.stash.put(key, reports) - return b"done" - - if request.method == u'GET': - response.headers.set(b'Content-Type', b'application/json') - return json.dumps(request.server.stash.take(key) or []) - - response.status = 400 - return b'invalid method' diff --git a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js index 01f835e12ad..8c62147a57a 100644 --- a/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js +++ b/tests/wpt/web-platform-tests/html/cross-origin-opener-policy/reporting/resources/reporting-common.js @@ -27,7 +27,7 @@ function isCoopOpenerBreakageReport(report) { async function pollReports(endpoint) { const res = await fetch( - `${directory}/report.py?endpoint=${endpoint.name}`, + `/reporting/resources/report.py?endpoint=${endpoint.name}`, {cache: 'no-store'}); if (res.status !== 200) { return; @@ -143,7 +143,7 @@ function getReportEndpoints(host) { 'group': `${reportEndpoint.name}`, 'max_age': 3600, 'endpoints': [ - {'url': `${host}/html/cross-origin-opener-policy/reporting/resources/report.py?endpoint=${reportEndpoint.name}` + {'url': `${host}/reporting/resources/report.py?endpoint=${reportEndpoint.name}` }, ] }; diff --git a/tests/wpt/web-platform-tests/html/interaction/focus/focused-element-move-documents-crash.html b/tests/wpt/web-platform-tests/html/interaction/focus/focused-element-move-documents-crash.html new file mode 100644 index 00000000000..ca9ab26edd7 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/interaction/focus/focused-element-move-documents-crash.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1172828"> + +<!-- No crash should occur if a focused element is moved to another document. --> + +<div id=editable contenteditable=> + +<script> +editable.addEventListener('blur', function() { + document.execCommand('InsertText', false, '\t'); +}); +editable.focus(); +const secondDoc = document.implementation.createDocument('', null); +secondDoc.appendChild(editable); +</script> diff --git a/tests/wpt/web-platform-tests/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html b/tests/wpt/web-platform-tests/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html new file mode 100644 index 00000000000..a7ae76f733a --- /dev/null +++ b/tests/wpt/web-platform-tests/html/interaction/focus/processing-model/preventScroll-nested-scroll-elements.html @@ -0,0 +1,62 @@ +<!doctype html> +<title>focus(options) - preventScroll on nested scroll elements</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<style> + .scrollBox { width: 400px; height: 300px; border: 1px solid } + .bigbox { width: 380px; height: 300px; border: 1px solid } + .box { width: 380px; height: 150px; border: 1px solid } +</style> +<div class="scrollBox" style="overflow-y: scroll;"> + <div class="bigbox" id="1" style="overflow-y: scroll;" tabindex=1> + <div class="box" id="1_1" tabindex=1>1_1</div> + <div class="box" id="1_2" tabindex=1>1_2</div> + <div class="box" id="1_3" tabindex=1>1_3</div> + </div> + <div class="bigbox" id="2" style="overflow-y: scroll;" tabindex=1> + <div class="box" id="2_1" tabindex=1>2_1</div> + <div class="box" id="2_2" tabindex=1>2_2</div> + <div class="box" id="2_3" tabindex=1>2_3</div> + </div> + <div class="bigbox" id="3" style="overflow-y: scroll;" tabindex=1> + <div class="box" id="3_1" tabindex=1>3_1</div> + <div class="box" id="3_2" tabindex=1>3_2</div> + <div class="box" id="3_3" tabindex=1>3_3</div> + </div> +</div> +<script> +promise_test(async function(t) { + await new Promise(resolve => window.addEventListener("load", resolve)); + let div2_2 = document.getElementById("2_2"); + div2_2.focus({ preventScroll: true }); + + await new Promise(resolve => { + requestAnimationFrame(() => requestAnimationFrame(resolve)); + }); + + assert_equals(document.activeElement, div2_2, `box 2_2: should have been focused`); + assert_equals(div2_2.scrollTop, 0, "box 2_2: should not have scrolled"); + assert_equals(div2_2.parentNode.scrollTop, 0, "box 2_2: should not have scrolled ancestor"); + + // Reset focus + let div1_1 = document.getElementById("1_1"); + div1_1.focus(); + + await new Promise(resolve => { + requestAnimationFrame(() => requestAnimationFrame(resolve)); + }); + assert_equals(document.activeElement, div1_1, `box 1_1: should have been focused`); + + let div2 = document.getElementById("2"); + div2.focus({ preventScroll: true }); + + await new Promise(resolve => { + requestAnimationFrame(() => requestAnimationFrame(resolve)); + }); + + assert_equals(document.activeElement, div2, `box 2: should have been focused`); + assert_equals(div2.scrollTop, 0, "box 2: should not have scrolled"); + assert_equals(div2_2.scrollTop, 0, "box 2_2: should not have scrolled"); + assert_equals(div2.parentNode.scrollTop, 0, "box 2: should not have scrolled ancestor"); +}); +</script> diff --git a/tests/wpt/web-platform-tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-min-intrinsic-size-ref.html b/tests/wpt/web-platform-tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-min-intrinsic-size-ref.html new file mode 100644 index 00000000000..7a79361fc39 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-min-intrinsic-size-ref.html @@ -0,0 +1,9 @@ +<!doctype html> +<title>Test reference</title> +<div style="width:200px; border: 2px solid purple;"> + <marquee style="border: 1px solid black; color: transparent;"> +Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt commodi +ratione iste tempore nemo mollitia exercitationem error cum excepturi sit ab +eius consectetur quasi possimus facere, iusto est impedit laborum. + </marquee> +</div> diff --git a/tests/wpt/web-platform-tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-min-intrinsic-size.html b/tests/wpt/web-platform-tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-min-intrinsic-size.html new file mode 100644 index 00000000000..827583d730a --- /dev/null +++ b/tests/wpt/web-platform-tests/html/obsolete/requirements-for-implementations/the-marquee-element-0/marquee-min-intrinsic-size.html @@ -0,0 +1,15 @@ +<!doctype html> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1692380"> +<title>Marquee min intrinsic size should not cause overflow</title> +<link rel="author" href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez"> +<link rel="author" href="https://mozilla.org" title="Mozilla"> +<link rel=match href="marquee-min-intrinsic-size-ref.html"> +<div style="width:200px; border: 2px solid purple;"> + <div style="display: inline-block;"> + <marquee style="border: 1px solid black; color: transparent;"> +Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt commodi +ratione iste tempore nemo mollitia exercitationem error cum excepturi sit ab +eius consectetur quasi possimus facere, iusto est impedit laborum. + </marquee> + </div> +</div> diff --git a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html index 91fdc6c86c5..816d84e4447 100644 --- a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html +++ b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/canvas-aspect-ratio.html @@ -2,6 +2,7 @@ <title>Canvas width and height attributes are used as the surface size</title> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> +<script src="resources/aspect-ratio.js"></script> <style> canvas { width: 100%; @@ -17,6 +18,10 @@ function assert_ratio(img, expected) { assert_approx_equals(parseInt(getComputedStyle(img).width, 10) / parseInt(getComputedStyle(img).height, 10), expected, epsilon); } +function test_computed_style(width, height, expected) { + test_computed_style_aspect_ratio("canvas", {width: width, height: height}, expected); +} + test(function() { canvas = document.getElementById("contained"); assert_ratio(canvas, 2.5); @@ -31,4 +36,21 @@ test(function() { // Canvases always use the aspect ratio from their surface size. assert_ratio(canvas, 2.5); }, "Canvas width and height attributes are used as the surface size"); + +test(function() { + test_computed_style("10", "20", "auto 10 / 20"); + test_computed_style("0", "1", "auto 0 / 1"); + test_computed_style("1", "0", "auto 1 / 0"); + test_computed_style("0", "0", "auto 0 / 0"); + test_computed_style("0.5", "1.5", "auto 0.5 / 1.5"); +}, "Computed style"); + +test(function() { + test_computed_style(null, null, "auto"); + test_computed_style("10", null, "auto"); + test_computed_style(null, "20", "auto"); + test_computed_style("xx", "20", "auto"); + test_computed_style("10%", "20", "auto"); +}, "Computed style for invalid ratios"); + </script> diff --git a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html index af4542e55fd..dd163fdf294 100644 --- a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html +++ b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html @@ -2,6 +2,7 @@ <title>Image width and height attributes are used to infer aspect-ratio</title> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> +<script src="resources/aspect-ratio.js"></script> <style> img { width: 100%; @@ -22,6 +23,14 @@ function assert_ratio(img, expected, description) { assert_approx_equals(parseFloat(getComputedStyle(img).width, 10) / parseFloat(getComputedStyle(img).height, 10), expected, epsilon, description); } + +function test_computed_style(width, height, expected) { + test_computed_style_aspect_ratio("img", {width: width, height: height}, expected); + test_computed_style_aspect_ratio("input", {type: "image", width: width, height: height}, expected); + // input type=submit should not do this mapping. + test_computed_style_aspect_ratio("input", {type: "submit", width: width, height: height}, "auto"); +} + // Create and append a new image and immediately check the ratio. // This is not racy because the spec requires the user agent to queue a task: // https://html.spec.whatwg.org/multipage/images.html#updating-the-image-data @@ -58,4 +67,21 @@ onload = t.step_func_done(function() { assert_not_equals(images[5].offsetHeight, 500, "Images with alt text should be inline and ignore the aspect ratio"); assert_ratio(images[6], 133/106, "The original aspect ratio of blue.png"); }); + +test(function() { + test_computed_style("10", "20", "auto 10 / 20"); + test_computed_style("0", "1", "auto 0 / 1"); + test_computed_style("1", "0", "auto 1 / 0"); + test_computed_style("0", "0", "auto 0 / 0"); + test_computed_style("0.5", "1.5", "auto 0.5 / 1.5"); +}, "Computed style"); + +test(function() { + test_computed_style(null, null, "auto"); + test_computed_style("10", null, "auto"); + test_computed_style(null, "20", "auto"); + test_computed_style("xx", "20", "auto"); + test_computed_style("10%", "20", "auto"); +}, "Computed style for invalid ratios"); + </script> diff --git a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/resources/aspect-ratio.js b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/resources/aspect-ratio.js new file mode 100644 index 00000000000..53226c38f5c --- /dev/null +++ b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/resources/aspect-ratio.js @@ -0,0 +1,10 @@ +function test_computed_style_aspect_ratio(tag, attributes, expected) { + var elem = document.createElement(tag); + for (name in attributes) { + let val = attributes[name]; + if (val !== null) + elem.setAttribute(name, val); + } + document.body.appendChild(elem); + assert_equals(getComputedStyle(elem).aspectRatio, expected); +} diff --git a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html index c81b70dbf4e..bfa91081e26 100644 --- a/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html +++ b/tests/wpt/web-platform-tests/html/rendering/replaced-elements/attributes-for-embedded-content-and-images/video-aspect-ratio.html @@ -3,6 +3,7 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/common/media.js"></script> +<script src="resources/aspect-ratio.js"></script> <style> video { width: 100%; @@ -19,6 +20,10 @@ function assert_ratio(img, expected) { assert_approx_equals(parseInt(getComputedStyle(img).width, 10) / parseInt(getComputedStyle(img).height, 10), expected, epsilon); } +function test_computed_style(width, height, expected) { + test_computed_style_aspect_ratio("video", {width: width, height: height}, expected); +} + t.step(function() { var video = document.getElementById("contained"); video.src = getVideoURI('/media/2x2-green'); @@ -44,4 +49,21 @@ t.step(function() { assert_ratio(video, 1); }); }, "aspect ratio for regular video"); + +test(function() { + test_computed_style("10", "20", "auto 10 / 20"); + test_computed_style("0", "1", "auto 0 / 1"); + test_computed_style("1", "0", "auto 1 / 0"); + test_computed_style("0", "0", "auto 0 / 0"); + test_computed_style("0.5", "1.5", "auto 0.5 / 1.5"); +}, "Computed style"); + +test(function() { + test_computed_style(null, null, "auto"); + test_computed_style("10", null, "auto"); + test_computed_style(null, "20", "auto"); + test_computed_style("xx", "20", "auto"); + test_computed_style("10%", "20", "auto"); +}, "Computed style for invalid ratios"); + </script> diff --git a/tests/wpt/web-platform-tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-currentSrc.html b/tests/wpt/web-platform-tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-currentSrc.html new file mode 100644 index 00000000000..b17190186db --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-currentSrc.html @@ -0,0 +1,80 @@ +<!doctype html> +<title>currentSrc should not be reset when changing source</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id=log></div> +<audio src="/media/sine440.mp3"></audio> +<script> +let v; +let t = async_test("Test currentSrc behaviour in various playback scenarios"); +v = document.querySelector('audio'); +function queueTaskAndStep(f) { + step_timeout(function() { + t.step(f); + }, 0); +} + +function next() { + let testcase = tests.shift(); + if (!testcase) { + t.done(); + } + step_timeout(testcase, 0); +} + +let tests = [ + function() { + v.src = "/media/sound_0.mp3"; + queueTaskAndStep(function() { + assert_true(v.currentSrc.indexOf("sound_0.mp3") != -1, "currentSrc must be equal to the source after load if present"); + next(); + }); + }, + function() { + v.src = URL.createObjectURL(new MediaSource()); + queueTaskAndStep(function() { + assert_equals(v.currentSrc, "", "currentSrc must be equal to the empty string after load if playing a MediaSource"); + next(); + }); + }, + function() { + v.src = "/media/sound_0.mp3"; + // Source should be ignored when there is an `src` + let sourceNode = document.createElement("source"); + sourceNode.setAttribute("src", "/media/sine440.mp3"); + sourceNode.setAttribute("type", "audio/mpeg"); + v.appendChild(sourceNode); + queueTaskAndStep(function() { + assert_true(v.currentSrc.indexOf("sine440.mp3") == -1, "The src attribute takes precedence over any source child element when both are preset"); + next(); + }) + }, + function() { + // But taken into account when there is no `src` attribute; + v.src = ""; + v.removeAttribute("src"); + queueTaskAndStep(function() { + assert_true(v.currentSrc.indexOf("sine440.mp3") != -1, "The child source element is the current source when no src attribute is present"); + next(); + }); + }, + function() { + v.firstChild.remove(); + v.src = "https://test:test/"; + queueTaskAndStep(function() { + assert_true(v.currentSrc.indexOf("sine440.mp3") != -1, "Not reset when a new load errors"); + next(); + }); + }, + function() { + v.srcObject = new MediaStream(); + queueTaskAndStep(function() { + assert_equals(v.currentSrc, "", "When playing a MediaStream, currentSrc should also be reset to an empty string"); + next(); + }); + } +]; + +next(); + +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-first-load-canceled-second-load-blank.html b/tests/wpt/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-first-load-canceled-second-load-blank.html new file mode 100644 index 00000000000..bed3b6477f5 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-first-load-canceled-second-load-blank.html @@ -0,0 +1,38 @@ +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +// Check about:blank navigation is asynchronous, even if the initial navigation +// is canceled. +promise_test(async test => { + // Wait for document.body to exist, before appending the <iframe>. + await new Promise(resolve => window.onload = resolve); + + // The initial navigation in this new iframe will result in a 204 No Content. + // As a result the navigation will be canceled. The <iframe> will stay on the + // initial empty document. + const iframe = document.createElement("iframe"); + iframe.src = "/common/blank.html?pipe=status(204)" + document.body.appendChild(iframe); + + // The navigation in the iframe will be canceled. There are no good ways to + // detect when it will happen. Anyway, any additional navigation must be + // asynchronous. To test what happens when there are no pending navigation, + // waiting one second should be enough, most of the time. + await new Promise(resolve => test.step_timeout(resolve, 1000)); + + let load_event_fired = false; + const load_event_promise = new Promise(resolve => { + iframe.onload = () => { + load_event_fired = true; + resolve(); + }; + }); + iframe.src = "about:blank"; + assert_equals(load_event_fired, false, + "about:blank must not commit synchronously"); + await load_event_promise; +}, "about:blank navigation is asynchronous, even if the initial one is " + + "canceled."); + +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js b/tests/wpt/web-platform-tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js index 693bcf9a2af..7df128515c9 100644 --- a/tests/wpt/web-platform-tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js +++ b/tests/wpt/web-platform-tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js @@ -1,14 +1,99 @@ -promise_test(() => { +test(t => { const form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input")); input.type = "file"; input.name = "hi"; + t.add_cleanup(() => { + document.body.removeChild(form); + }); const fd = new FormData(form), value = fd.get(input.name); assert_true(value instanceof File, "value is a File"); assert_equals(value.name, "", "name"); assert_equals(value.type, "application/octet-stream", "type"); - return new Response(value).text().then(body => { - assert_equals(body, "", "body"); + assert_equals(value.size, 0, "expected value to be an empty File"); +}, "Empty <input type=file> is still added to the form's entry list"); + +async_test((t) => { + const form = document.body.appendChild(document.createElement("form")), + input = form.appendChild(document.createElement("input")), + target = document.createElement("iframe"); + target.name = "target1"; + document.body.appendChild(target); + form.method = "POST"; + form.action = "/fetch/api/resources/echo-content.py"; + form.enctype = "application/x-www-form-urlencoded"; + form.target = target.name; + input.type = "file"; + input.name = "hi"; + t.add_cleanup(() => { + document.body.removeChild(form); + document.body.removeChild(target); + }); + + target.addEventListener("load", t.step_func_done(() => { + assert_equals(target.contentDocument.body.textContent, "hi="); + })); + form.submit(); +}, "Empty <input type=file> shows up in the urlencoded serialization"); + +async_test((t) => { + const form = document.body.appendChild(document.createElement("form")), + input = form.appendChild(document.createElement("input")), + target = document.createElement("iframe"); + target.name = "target2"; + document.body.appendChild(target); + form.method = "POST"; + form.action = "/fetch/api/resources/echo-content.py"; + form.enctype = "multipart/form-data"; + form.target = target.name; + input.type = "file"; + input.name = "hi"; + t.add_cleanup(() => { + document.body.removeChild(form); + document.body.removeChild(target); + }); + + target.addEventListener("load", t.step_func_done(() => { + // We use \n rather than \r\n because newlines get normalized as a result + // of HTML parsing. + const found = target.contentDocument.body.textContent; + const boundary = found.split("\n")[0]; + const expected = [ + boundary, + 'Content-Disposition: form-data; name="hi"; filename=""', + "Content-Type: application/octet-stream", + "", + "", + boundary + "--", + "", + ].join("\n"); + assert_equals(found, expected); + })); + form.submit(); +}, "Empty <input type=file> shows up in the multipart/form-data serialization"); + +async_test((t) => { + const form = document.body.appendChild(document.createElement("form")), + input = form.appendChild(document.createElement("input")), + target = document.createElement("iframe"); + target.name = "target3"; + document.body.appendChild(target); + form.method = "POST"; + form.action = "/fetch/api/resources/echo-content.py"; + form.enctype = "text/plain"; + form.target = target.name; + input.type = "file"; + input.name = "hi"; + t.add_cleanup(() => { + document.body.removeChild(form); + document.body.removeChild(target); }); -}, "Empty <input type=file> is still serialized"); + + target.addEventListener("load", t.step_func_done(() => { + // The actual result is "hi=\r\n"; the newline gets normalized as a side + // effect of the HTML parsing. + assert_equals(target.contentDocument.body.textContent, "hi=\n"); + })); + form.submit(); +}, "Empty <input type=file> shows up in the text/plain serialization"); diff --git a/tests/wpt/web-platform-tests/html/semantics/forms/the-select-element/select-add-option-crash.html b/tests/wpt/web-platform-tests/html/semantics/forms/the-select-element/select-add-option-crash.html new file mode 100644 index 00000000000..78e9e7de53f --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/forms/the-select-element/select-add-option-crash.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<link rel=author href="mailto:jarhar@chromium.org"> +<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1178128"> + +<script> +function iframeloadhandler() { + selectid[5] = optionid; +} +</script> +<option id="optionid" selected> + <select id="selectid"> + <select> + <iframe onload="iframeloadhandler()"> diff --git a/tests/wpt/web-platform-tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html b/tests/wpt/web-platform-tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html new file mode 100644 index 00000000000..79db3864586 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/forms/the-selectmenu-element/selectmenu-popup.tentative.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<title>HTMLSelectMenuElement Test: popup</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<selectmenu id="selectMenu0"> + <option>one</option> + <option id="selectMenu0-child2">two</option> + <div id="selectMenu0-child3">I'm a div with no part attr</div> + <option>three</option> + <option>four</option> +</selectmenu> + +<selectmenu id="selectMenu1"> + <div slot="button" part="button" id="selectMenu1-button"> + Custom button + </div> + <popup slot="listbox" part="listbox"> + <option>one</option> + <option id="selectMenu1-child2">two</option> + <div part="option" id="selectMenu1-child3">three</div> + </popup> +</selectmenu> + +<selectmenu id="selectMenu2"> + <!-- Swap out the listbox part without providing a replacement --> + <div slot="listbox"></div> +</selectmenu> +<script> + + function clickOn(element) { + const actions = new test_driver.Actions(); + return actions.pointerMove(0, 0, {origin: element}) + .pointerDown({button: actions.ButtonType.LEFT}) + .pointerUp({button: actions.ButtonType.LEFT}) + .send(); + } + + promise_test(async () => { + const selectMenu0 = document.getElementById("selectMenu0"); + const selectMenu0Child2 = document.getElementById("selectMenu0-child2"); + const selectMenu0Child3 = document.getElementById("selectMenu0-child3"); + assert_equals(selectMenu0.value, "one"); + await clickOn(selectMenu0); + await clickOn(selectMenu0Child2); + assert_equals(selectMenu0.value, "two"); + + await clickOn(selectMenu0); + await clickOn(selectMenu0Child3); + assert_equals(selectMenu0.value, "two", "Clicking a non-option should not change the value"); + }, "Opening the popup and clicking an option should change the selectmenu's value"); + + promise_test(async () => { + const selectMenu1 = document.getElementById("selectMenu1"); + const selectMenu1Button = document.getElementById("selectMenu1-button"); + const selectMenu1Child2 = document.getElementById("selectMenu1-child2"); + const selectMenu1Child3 = document.getElementById("selectMenu1-child3"); + assert_equals(selectMenu1.value, "one"); + await clickOn(selectMenu1Button); + await clickOn(selectMenu1Child2); + assert_equals(selectMenu1.value, "two", "Clicking an <option> should change the value"); + + await clickOn(selectMenu1Button); + await clickOn(selectMenu1Child3); + assert_equals(selectMenu1.value, "three", "Clicking a <div part='option'> should change the value"); + }, "With custom button and popup: opening the popup and clicking an option should change the selectmenu's value"); + + promise_test(async () => { + const selectMenu2 = document.getElementById("selectMenu2"); + await clickOn(selectMenu2); + assert_equals(selectMenu2.value, ""); + }, "Clicking a popup with no listbox part does nothing"); +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/forms/the-selectmenu-element/selectmenu-value.tentative.html b/tests/wpt/web-platform-tests/html/semantics/forms/the-selectmenu-element/selectmenu-value.tentative.html new file mode 100644 index 00000000000..b77d3303ede --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/forms/the-selectmenu-element/selectmenu-value.tentative.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> +<title>HTMLSelectMenuElement Test: value</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<selectmenu id="selectMenu0"></selectmenu> + +<selectmenu id="selectMenu1"> + <option>one</option> + <option>two</option> + <div>I'm a div with no part attr</div> + <option>three</option> + <option>four</option> +</selectmenu> + +<selectmenu id="selectMenu2"> + <div part="option">one</div> + <div part="option">two</div> + <div>I'm a div with no part attr</div> + <div part="option">three</div> + <div part="option">four</div> +</selectmenu> + +<selectmenu id="selectMenu3"> + <div>I'm a div with no part attr</div> + <option id="selectMenu3-child1">one</option> + <div part="option" id="selectMenu3-child2">two</div> + <option id="selectMenu3-child3">three</option> +</selectmenu> + +<selectmenu id="selectMenu4"> + <div part="option" id="selectMenu4-child1">two</div> + <div part="option" id="selectMenu4-child2">two</div> +</selectmenu> + +<selectmenu id="selectMenu5"> + <div slot="button" part="button"> + <div part="selected-value" id="selectMenu5-custom-selected-value">Default custom selected-value text</div> + </div> + <option>one</option> + <option>two</option> +</selectmenu> + +<selectmenu id="selectMenu6"> + <div slot="button" part="button"> + <div part="selected-value" id="selectMenu6-custom-selected-value">Default custom selected-value text</div> + </div> + <popup slot="listbox" part="listbox"> + <option>one</option> + <div part="option">two</div> + </popup> +</selectmenu> + +<script> + +test(() => { + const selectMenu0 = document.getElementById("selectMenu0"); + assert_equals(selectMenu0.value, ""); + selectMenu0.value = "something"; + assert_equals(selectMenu0.value, "", "Setting value should have no effect if there is no matching option"); +}, "Test that HTMLSelectMenu with no options has empty string for value"); + +test(() => { + const selectMenu1 = document.getElementById("selectMenu1"); + assert_equals(selectMenu1.value, "one", "value should start with the text of the first option part"); + + selectMenu1.value = "three"; + assert_equals(selectMenu1.value, "three", "value can be set to the text of an option part"); + + selectMenu1.value = "I'm a div with no part attr"; + assert_equals(selectMenu1.value, "three", "Setting value should have no effect if there is no matching option"); +}, "Test value with HTMLOptionElement element option parts"); + +test(() => { + const selectMenu2 = document.getElementById("selectMenu2"); + assert_equals(selectMenu2.value, "one", "value should start with the text of the first option part"); + + selectMenu2.value = "three"; + assert_equals(selectMenu2.value, "three", "value can be set to the text of an option part"); + + selectMenu2.value = "I'm a div with no part attr"; + assert_equals(selectMenu2.value, "three", "Setting value should have no effect if there is no matching option"); +}, "Test value with non-HTMLOptionElement element option parts"); + +test(() => { + const selectMenu3 = document.getElementById("selectMenu3"); + assert_equals(selectMenu3.value, "one", "value should start with the text of the first option part"); + + document.getElementById("selectMenu3-child3").remove(); + assert_equals(selectMenu3.value, "one", "Removing a non-selected option should not change the value"); + + document.getElementById("selectMenu3-child1").remove(); + assert_equals(selectMenu3.value, "two", "When the selected option is removed, the new first option should become selected"); + + document.getElementById("selectMenu3-child2").remove(); + assert_equals(selectMenu3.value, "", "When all options are removed, value should be the empty string"); +}, "Test that value is updated when options are removed"); + +test(() => { + const selectMenu4 = document.getElementById("selectMenu4"); + assert_equals(selectMenu4.value, "one", "value should start with the text of the first option part"); + + document.getElementById("selectMenu4-child1").setAttribute("part", "notOption"); + assert_equals(selectMenu4.value, "two", "Changing the part attribute of the selected option should remove its status as the selected option"); +}, "Test that value is updated when the part attribute is removed"); + +test(() => { + const selectMenu5 = document.getElementById("selectMenu5"); + let customSelectedValuePart = document.getElementById("selectMenu5-custom-selected-value"); + assert_equals(selectMenu5.value, "one", "value should start with the text of the first option part"); + assert_equals(customSelectedValuePart.innerText, "one", "Custom selected value part should be set to initial value of selectmenu"); + + selectMenu5.value = "two"; + assert_equals(customSelectedValuePart.innerText, "two", "Custom selected value part should be updated when value of selectmenu changes"); +}, "Test that slotted-in selected-value part is updated to value of selectmenu"); + +test(() => { + const selectMenu6 = document.getElementById("selectMenu6"); + let customSelectedValuePart = document.getElementById("selectMenu6-custom-selected-value"); + assert_equals(selectMenu6.value, "one", "value should start with the text of the first option part"); + assert_equals(customSelectedValuePart.innerText, "one", "Custom selected value part should be set to initial value of selectmenu"); + + selectMenu6.value = "two"; + assert_equals(customSelectedValuePart.innerText, "two", "Custom selected value part should be updated when value of selectmenu changes"); +}, "Test that option parts in a slotted-in listbox are reflected in the value property"); + +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-anchor-nesting.tentative.html b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-anchor-nesting.tentative.html new file mode 100644 index 00000000000..00b1140f575 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-anchor-nesting.tentative.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Popup anchor nesting</title> +<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org"> +<link rel=help href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<!-- This example has the anchor (b1) for one popup (p1) + which contains a separate popup (p2) which is anchored + by a separate anchor (b2). --> +<button id=b1 onclick='p1.show()'>Popup 1 + <popup id=p2 anchor=b2> + <span id=inside2>Inside popup 2</span> + </popup> +</button> +<popup id=p1 anchor=b1>This is popup 1</popup> +<button id=b2 onclick='p2.show()'>Popup 2</button> + +<style> + #p1 { top:50px; } + #p2 { top:50px; left:250px; } + popup { border: 5px solid red; } +</style> + + +<script> + function clickOn(element) { + const actions = new test_driver.Actions(); + return actions.pointerMove(0, 0, {origin: element}) + .pointerDown({button: actions.ButtonType.LEFT}) + .pointerUp({button: actions.ButtonType.LEFT}) + .send(); + } + + const popup1 = document.querySelector('#p1'); + const button1 = document.querySelector('#b1'); + const popup2 = document.querySelector('#p2'); + + (async function() { + setup({ explicit_done: true }); + + popup1.show(); + assert_true(popup1.open); + popup2.show(); + assert_true(popup2.open); + assert_false(popup1.open,'Popups are not nested, so popup1 should close'); + await clickOn(button1); + test(t => { + // Button1 is the anchor for popup1, and an ancestor of popup2. + // Since popup2 is open, but not popup1, button1 should not be + // the anchor of any open popup. So popup2 should be closed. + assert_false(popup2.open); + },'Nested popups (inside anchor elements) do not affect light dismiss'); + + done(); + })(); +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-light-dismiss-on-scroll.tentative.html b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-light-dismiss-on-scroll.tentative.html new file mode 100644 index 00000000000..3f35ec24dcd --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-light-dismiss-on-scroll.tentative.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<html lang="en"> +<meta charset="utf-8" /> +<title>Popup light dismiss on scroll</title> +<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org"> +<link rel=help href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<div id=scroller> + Scroll me<br><br> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Enim ut sem viverra aliquet eget sit amet tellus. Massa + sed elementum tempus egestas sed sed risus pretium. Felis bibendum ut tristique et egestas + quis. Tortor dignissim convallis aenean et. Eu mi bibendum neque egestas congue quisque +</div> + +<popup id=popup1>This is popup 1<div id=anchor></div></popup> +<popup id=popup2 anchor=anchor>This is popup 2</popup> +<button onclick='popup1.show();popup2.show();'>Open popups</button> + +<style> + #popup1 { top:50px; left: 50px; } + #popup2 { top:150px; left: 50px; } + #scroller { + height: 150px; + width: 150px; + overflow-y: scroll; + border: 1px solid black; + } +</style> + +<script> + async_test(t => { + popup1.addEventListener('hide',e => { + assert_false(popup2.open); + t.done(); + }) + assert_false(popup1.open); + assert_false(popup2.open); + popup1.show(); + popup2.show(); + assert_true(popup1.open); + assert_true(popup2.open); + scroller.scrollTo(0, 100); + },'Scrolling light-dismisses all popups'); +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-light-dismiss.tentative.html b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-light-dismiss.tentative.html new file mode 100644 index 00000000000..f2a55aa1dec --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-light-dismiss.tentative.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Popup light dismiss behavior</title> +<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org"> +<link rel=help href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + + +<button id=b1 onclick='p1.show()'>Popup 1</button> +<span id=outside>Outside all popups</span> +<popup id=p1 anchor=b1> + <span id=inside1>Inside popup 1</span> + <button id=b2 onclick='p2.show()'>Popup 2</button> +</popup> +<popup id=p2 anchor=b2> + <span id=inside2>Inside popup 2</span> +</popup> + +<style> + #p1 { top:50px; } + #p2 { top:50px; left:250px; } + popup { border: 5px solid red; } +</style> + + +<script> + function clickOn(element) { + const actions = new test_driver.Actions(); + return actions.pointerMove(0, 0, {origin: element}) + .pointerDown({button: actions.ButtonType.LEFT}) + .pointerUp({button: actions.ButtonType.LEFT}) + .send(); + } + + const popup1 = document.querySelector('#p1'); + const button1 = document.querySelector('#b1'); + const popup2 = document.querySelector('#p2'); + const outside = document.querySelector('#outside'); + const inside1 = document.querySelector('#inside1'); + const inside2 = document.querySelector('#inside2'); + + (async function() { + setup({ explicit_done: true }); + + let popup1HideCount = 0; + popup1.addEventListener('hide',(e) => { + ++popup1HideCount; + e.preventDefault(); // 'hide' should not be cancellable. + }); + let popup2HideCount = 0; + popup2.addEventListener('hide',(e) => { + ++popup2HideCount; + e.preventDefault(); // 'hide' should not be cancellable. + }); + + assert_false(popup1.open); + popup1.show(); + let p1HideCount = popup1HideCount; + assert_true(popup1.open); + await clickOn(outside); + test(t => { + assert_false(popup1.open); + assert_equals(popup1HideCount,p1HideCount+1); + },'Clicking outside a popup will dismiss the popup'); + + assert_false(popup1.open); + popup1.show(); + p1HideCount = popup1HideCount; + await clickOn(inside1); + test(t => { + assert_true(popup1.open); + assert_equals(popup1HideCount,p1HideCount); + },'Clicking inside a popup does not close that popup'); + + popup2.show(); + p1HideCount = popup1HideCount; + let p2HideCount = popup2HideCount; + await clickOn(inside2); + test(t => { + assert_true(popup1.open); + assert_true(popup2.open); + assert_equals(popup1HideCount,p1HideCount,'popup1'); + assert_equals(popup2HideCount,p2HideCount,'popup2'); + },'Clicking inside a child popup shouldn\'t close either popup'); + + popup2.show(); + p1HideCount = popup1HideCount; + p2HideCount = popup2HideCount; + await clickOn(inside1); + test(t => { + assert_true(popup1.open); + assert_equals(popup1HideCount,p1HideCount); + assert_false(popup2.open); + assert_equals(popup2HideCount,p2HideCount+1); + },'Clicking inside a parent popup should close child popup'); + + p1HideCount = popup1HideCount; + await clickOn(button1); + test(t => { + assert_true(popup1.open); + assert_equals(popup1HideCount,p1HideCount); + },'Clicking on anchor element shouldn\'t close its popup'); + + done(); + })(); +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-open-overflow-display-ref.tentative.html b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-open-overflow-display-ref.tentative.html new file mode 100644 index 00000000000..f324f573b3b --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-open-overflow-display-ref.tentative.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel=author title="Mason Freed" href="mailto:masonfreed@chromium.org"> + +<popup id=p1>This is popup 1<div id=anchor2></div></popup> +<popup id=p2 anchor=anchor2>This is popup 2<div id=anchor3></div></popup> +<popup id=p3 anchor=anchor3>This is popup 3</popup> + +<style> + popup#p2 { + top: 100px; + } + popup#p3 { + top:200px; + } + popup { + width: fit-content; + height: fit-content; + border: 1px solid; + padding: 1em; + background: white; + color: black; + } +</style> + +<script> + document.querySelector('#p1').show(); + document.querySelector('#p2').show(); + document.querySelector('#p3').show(); +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-open-overflow-display.tentative.html b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-open-overflow-display.tentative.html new file mode 100644 index 00000000000..eae82889adc --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/interactive-elements/the-popup-element/popup-open-overflow-display.tentative.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<link rel=author title="Mason Freed" href="mailto:masonfreed@chromium.org"> +<link rel=help href="https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Popup/explainer.md"> +<link rel=match href="popup-open-overflow-display-ref.tentative.html"> + +<div id=container> + <popup id=p1>This is popup 1<div id=anchor2></div></popup> + <popup id=p2 anchor=anchor2>This is popup 2<div id=anchor3></div></popup> + <popup id=p3 anchor=anchor3>This is popup 3</popup> +</div> + +<style> + #container { + overflow:hidden; + position: absolute; + top: 100px; + left: 50px; + width: 30px; + height: 30px; + } + popup#p2 { + position: absolute; + top: 100px; + } + popup#p3 { + position: relative; + top:200px; + } + popup { + width: fit-content; + height: fit-content; + border: 1px solid; + padding: 1em; + background: white; + color: black; + } +</style> + +<script> + document.querySelector('#p1').show(); + document.querySelector('#p2').show(); + document.querySelector('#p3').show(); +</script> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html index 4ca2bb70899..207d553c69e 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-basic.html @@ -54,5 +54,18 @@ }); document.body.appendChild(iframe); }, "Malformed CSS should not load"); + + async_test(function (test) { + const iframe = document.createElement("iframe"); + iframe.src = "resources/css-module-without-assertion-iframe.html"; + iframe.onload = test.step_func_done(function () { + assert_equals(iframe.contentDocument.window_onerror, undefined); + assert_equals(iframe.contentDocument.script_onerror.type, "error"); + assert_not_equals(getComputedStyle(iframe.contentDocument.querySelector('#test')) + .backgroundColor, "rgb(255, 0, 0)", + "CSS module without type assertion should result in a fetch error"); + }); + document.body.appendChild(iframe); + }, "CSS module without type assertion should result in a fetch error"); </script> </body> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html new file mode 100644 index 00000000000..4fbc11180fa --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/import-css-module-dynamic.html @@ -0,0 +1,22 @@ +<!doctype html> + +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> + +<body> + <script> + promise_test(async function (test) { + const css_module = await import("./resources/basic.css", { assert: { type: "css" }}); + assert_true(css_module.default instanceof CSSStyleSheet); + assert_equals(css_module.default.cssRules[0].cssText, "#test { background-color: red; }"); + }, "Load a CSS module with dynamic import()"); + + promise_test(function (test) { + return promise_rejects_js(test, TypeError, + import("./resources/basic.css"), + "Attempting to import() a CSS module without a type assertion should fail"); + }, "Ensure that loading a CSS module with dymnamic import() fails without a type assertion"); + </script> +</body> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-at-import-iframe.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-at-import-iframe.html index 86e7af7d51d..cce9e2163d7 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-at-import-iframe.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-at-import-iframe.html @@ -8,7 +8,7 @@ }; </script> <script type="module"> - import v from "./bad-import.css"; + import v from "./bad-import.css" assert { type: "css" }; document.adoptedStyleSheets = [v]; </script> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-iframe.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-iframe.html index 3a555c39271..e093d39898f 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-iframe.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-iframe.html @@ -8,7 +8,7 @@ }; </script> <script type="module"> - import v from "./basic.css"; + import v from "./basic.css" assert { type: "css" }; document.adoptedStyleSheets = [v]; </script> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-large-iframe.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-large-iframe.html index cc5b660e4cb..0cf11e9139f 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-large-iframe.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-basic-large-iframe.html @@ -8,7 +8,7 @@ }; </script> <script type="module"> - import v from "./basic-large.css"; + import v from "./basic-large.css" assert { type: "css" }; document.adoptedStyleSheets = [v]; </script> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-without-assertion-iframe.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-without-assertion-iframe.html new file mode 100644 index 00000000000..3d1be841cee --- /dev/null +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/css-module-without-assertion-iframe.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<body> + <script> + window.onerror = function (errorMsg, url, lineNumber, column, errorObj) + { + document.window_onerror = errorObj.name; + return true; + }; + + function scriptErrorHandler(e) { + document.script_onerror = e; + } + </script> + <script type="module" onerror="scriptErrorHandler(event)"> + import v from "./basic.css"; + document.adoptedStyleSheets = [v]; + </script> + + <div id="test"> + I am a test div. + </div> +</body>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed-iframe.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed-iframe.html index 471edd680cf..f5c64f6b59e 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed-iframe.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/malformed-iframe.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <body> <script type="module"> - import v from "./malformed.css"; + import v from "./malformed.css" assert { type: "css" }; document.adoptedStyleSheets = [v]; </script> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.js b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.js index 9a3b0bb105b..6f6852ce550 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.js +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker-dynamic-import.js @@ -1,3 +1,3 @@ -import("./basic.css") +import("./basic.css", { assert: { type: "css" } }) .then(() => postMessage("LOADED")) .catch(e => postMessage("NOT LOADED"));
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.js b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.js index 397a12c3b53..c97d9652d35 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.js +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/resources/worker.js @@ -1,2 +1,2 @@ -import "./basic.css"; +import "./basic.css" assert { type: "css" }; postMessage("Unexpectedly loaded");
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/utf8.tentative.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/utf8.tentative.html index f71339b4d59..6adcd716328 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/utf8.tentative.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/css-module/utf8.tentative.html @@ -17,18 +17,18 @@ const t3 = async_test("windows-1252"); const t4 = async_test("utf-7"); </script> <script type="module" onerror="t1.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=utf-8"; +import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=utf-8" assert { type: "css" }; check(t1, v); </script> <script type="module" onerror="t2.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=shift-jis"; +import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=shift-jis" assert { type: "css" }; check(t2, v); </script> <script type="module" onerror="t3.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=windows-1252"; +import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=windows-1252" assert { type: "css" }; check(t3, v); </script> <script type="module" onerror="t4.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=utf-7"; +import v from "../serve-with-content-type.py?fn=css-module/resources/utf8.css&ct=text/css%3Bcharset=utf-7" assert { type: "css" }; check(t4, v); </script> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.tentative.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.tentative.html index 2e1f9d8179c..cc47da1499f 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.tentative.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/json-module-service-worker-test.https.tentative.html @@ -10,11 +10,11 @@ assert_not_equals(reg.installing, undefined); }, "Javascript importing JSON Module should load within the context of a service worker"); - promise_test(async (test) => { - const reg = await navigator.serviceWorker.register('./module.json', { type: 'module' }); - test.add_cleanup(() => reg.unregister()); - assert_not_equals(reg.installing, undefined); - }, "JSON Modules should load within the context of a service worker"); + promise_test(test => { + return promise_rejects_dom(test, "SecurityError", + navigator.serviceWorker.register('./module.json', { type: 'module' }), + "Attempting to load JSON as a service worker should fail"); + }, "Trying to register a service worker with a top-level JSON Module should fail"); promise_test(async (test) => { const reg = await navigator.serviceWorker.register('./serviceworker-dynamic-import.js', { type: 'module' }); diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/module.tentative.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/module.tentative.html index 93243853226..a495d4ac186 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/module.tentative.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/module.tentative.html @@ -8,7 +8,7 @@ const t = async_test(); </script> <script type="module" onerror="t.step(() => assert_unreached(event))"> -import v from "./module.json"; +import v from "./module.json" assert { type: "json" }; t.step(() => { assert_equals(typeof v, "object"); assert_array_equals(Object.keys(v), ["test"]); diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.js b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.js index dcbe60f2c2f..6d507177e1e 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.js +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/non-object.tentative.any.js @@ -2,13 +2,13 @@ for (const value of [null, true, false, "string"]) { promise_test(async t => { - const result = await import(`./${value}.json`); + const result = await import(`./${value}.json`, { assert: { type: "json" } }); assert_equals(result.default, value); }, `Non-object: ${value}`); } promise_test(async t => { - const result = await import("./array.json"); + const result = await import("./array.json", { assert: { type: "json" } }); assert_array_equals(result.default, ["en", "try"]); }, "Non-object: array"); diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js index 3c7d29488db..9466c6fbe40 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker-dynamic-import.js @@ -1,5 +1,5 @@ onmessage = e => { - e.waitUntil(import("./module.json") + e.waitUntil(import("./module.json", { assert: { type: "json" } }) .then(module => e.source.postMessage("LOADED")) .catch(error => e.source.postMessage("FAILED"))); };
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js index 6d71f0175c7..3f0a4d16640 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/serviceworker.js @@ -1 +1 @@ -import './module.json';
\ No newline at end of file +import './module.json' assert { type: "json" };
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative.html index 1c0360b17e3..24a6f109e1c 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/utf8.tentative.html @@ -19,18 +19,18 @@ const t3 = async_test("windows-1252"); const t4 = async_test("utf-7"); </script> <script type="module" onerror="t1.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=utf-8"; +import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=utf-8" assert { type: "json"}; check(t1, v); </script> <script type="module" onerror="t2.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=shift-jis"; +import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=shift-jis" assert { type: "json"}; check(t2, v); </script> <script type="module" onerror="t3.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=windows-1252"; +import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=windows-1252" assert { type: "json"}; check(t3, v); </script> <script type="module" onerror="t4.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=utf-7"; +import v from "../serve-with-content-type.py?fn=json-module/utf8.json&ct=text/json%3Bcharset=utf-7" assert { type: "json"};; check(t4, v); </script> diff --git a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative.html b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative.html index 78e8b1d23fe..ff5953cb700 100644 --- a/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative.html +++ b/tests/wpt/web-platform-tests/html/semantics/scripting-1/the-script-element/json-module/valid-content-type.tentative.html @@ -19,18 +19,18 @@ const t3 = async_test("text/html+json"); const t4 = async_test("image/svg+json"); </script> <script type="module" onerror="t1.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/json"; +import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/json" assert { type: "json"}; check(t1, v); </script> <script type="module" onerror="t2.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=application/json"; +import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=application/json" assert { type: "json"}; check(t2, v); </script> <script type="module" onerror="t3.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/html+json"; +import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=text/html+json" assert { type: "json"}; check(t3, v); </script> <script type="module" onerror="t4.step(() => assert_unreached(event))"> -import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=image/svg+json"; +import v from "../serve-with-content-type.py?fn=json-module/module.json&ct=image/svg+json" assert { type: "json"}; check(t4, v); </script> diff --git a/tests/wpt/web-platform-tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-pluginarray.html b/tests/wpt/web-platform-tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-pluginarray.html index ab4f61ee9f9..cd5fc41856c 100644 --- a/tests/wpt/web-platform-tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-pluginarray.html +++ b/tests/wpt/web-platform-tests/html/webappapis/system-state-and-capabilities/the-navigator-object/navigator-pluginarray.html @@ -1,74 +1,24 @@ -<!doctype html> -<html> -<body> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script type="text/javascript"> -test(function () { - for (var i = 0; i < navigator.plugins.length; i++) { - const name = navigator.plugins[i].name; - const descriptor = Object.getOwnPropertyDescriptor(navigator.plugins, name); - assert_false(descriptor.enumerable); - assert_false(name in Object.keys(navigator.plugins)); - assert_in_array(name, Object.getOwnPropertyNames(navigator.plugins)); - assert_true(name in navigator.plugins); - } - for (var i = 0; i < navigator.mimeTypes.length; i++) { - const type = navigator.mimeTypes[i].type; - const descriptor = Object.getOwnPropertyDescriptor(navigator.mimeTypes, type); - assert_false(descriptor.enumerable); - assert_false(type in Object.keys(navigator.mimeTypes)); - assert_in_array(type, Object.getOwnPropertyNames(navigator.mimeTypes)); - assert_true(type in navigator.mimeTypes); - } -}, "Tests that named properties are not enumerable in navigator.plugins and navigator.mimeTypes"); - -test(function () { - for (var i = 0; i < navigator.plugins.length; i++) { - var plugin = navigator.plugins[i]; - var name = plugin.name; - assert_equals(plugin, navigator.plugins[i]); - assert_equals(plugin, navigator.plugins[name]); - } - for (var i = 0; i < navigator.mimeTypes.length; i++) { - var mime_type = navigator.mimeTypes[i]; - var type = mime_type.type; - assert_equals(mime_type, navigator.mimeTypes[i]); - assert_equals(mime_type, navigator.mimeTypes[type]); - assert_equals(mime_type.enabledPlugin, navigator.plugins[mime_type.enabledPlugin.name]); - } -}, "Tests that navigator.plugins and navigator.mimeTypes returns the same object when queried multiple times."); +<!DOCTYPE html> +<meta charset=utf-8> +<title>Test for empty plugins and mimeTypes arrays</title> +<link rel='author' title='Mason Freed' href='mailto:masonfreed@chromium.org'> +<link rel='help' href='https://github.com/whatwg/html/issues/6003'> +<script src='/resources/testharness.js'></script> +<script src='/resources/testharnessreport.js'></script> +<script> test(function () { - var iframe = document.createElement("iframe"); - iframe.src = "about:blank"; - document.body.appendChild(iframe); - assert_equals(navigator.plugins.length, iframe.contentWindow.navigator.plugins.length); - assert_equals(navigator.mimeTypes.length, iframe.contentWindow.navigator.mimeTypes.length); - for (var i = 0; i < navigator.plugins.length; i++) { - var plugin = navigator.plugins[i]; - var name = plugin.name; - assert_not_equals(plugin, iframe.contentWindow.navigator.plugins[i]); - assert_not_equals(plugin, iframe.contentWindow.navigator.plugins[name]); - } - for (var i = 0; i < navigator.mimeTypes.length; i++) { - var mime_type = navigator.mimeTypes[i]; - var type = mime_type.type; - assert_not_equals(mime_type, iframe.contentWindow.navigator.mimeTypes[i]); - assert_not_equals(mime_type, iframe.contentWindow.navigator.mimeTypes[type]); - assert_not_equals(mime_type.enabledPlugin, iframe.contentWindow.navigator.plugins[mime_type.enabledPlugin.name]); - } - iframe.remove(); -}, "Tests that navigator.plugins and navigator.mimeTypes does not return the same object on different frames."); + assert_equals(navigator.plugins.length, 0, "Plugins array must be empty"); + assert_equals(navigator.mimeTypes.length, 0, "mimeTypes array must be empty"); + navigator.plugins.refresh(); + assert_equals(navigator.plugins.length, 0, "Plugins array must be empty, even after refresh()"); + assert_equals(navigator.mimeTypes.length, 0, "mimeTypes array must be empty, even after refresh()"); +}, "Tests that navigator.plugins and mimeTypes both return empty arrays."); -test(function () { - for (var i = 1; i < navigator.plugins.length; i++) { - assert_less_than_equal(navigator.plugins[i-1].name.localeCompare(navigator.plugins[i].name), 0); - } - for (var i = 1; i < navigator.mimeTypes.length; i++) { - assert_less_than_equal(navigator.mimeTypes[i-1].type.localeCompare(navigator.mimeTypes[i].type), 0); - } -}, "Tests that navigator.plugins and navigator.mimeTypes returns plugins sorted in alphabetical order by plugin name."); +test(() => { + const arrayIterator = Array.prototype[Symbol.iterator]; + assert_equals(navigator.plugins[Symbol.iterator], arrayIterator, "plugins array must provide @@iterator"); + assert_equals(navigator.mimeTypes[Symbol.iterator], arrayIterator, "mimeTypes array must provide @@iterator"); + assert_equals(Plugin.prototype[Symbol.iterator], arrayIterator, "Plugin must provide @@iterator"); +}, "@@iterator must exist"); </script> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html new file mode 100644 index 00000000000..693cde21926 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/confirm-different-origin-frame.sub.html @@ -0,0 +1,24 @@ +<html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + setup({ single_test: true }); + function handleEvent(e) { + assert_equals(e.data, 'pass'); + done(); + } + function on_iframe_load() { + var frameWin = document.getElementById('confirmFrame').contentWindow; + frameWin.postMessage('Confirm', '*'); + } + window.addEventListener('message', handleEvent); +</script> + +<body> + <iframe id='confirmFrame' + src='http://{{hosts[alt][www]}}:{{ports[http][0]}}/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html' + onload='on_iframe_load()'></iframe> +</body> + +</html> diff --git a/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html new file mode 100644 index 00000000000..151def9b5a5 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/prompt-different-origin-frame.sub.html @@ -0,0 +1,24 @@ +<html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script> + setup({ single_test: true }); + function handleEvent(e) { + assert_equals(e.data, 'pass'); + done(); + } + function on_iframe_load() { + var frameWin = document.getElementById('promptFrame').contentWindow; + frameWin.postMessage('Prompt', '*'); + } + window.addEventListener('message', handleEvent); +</script> + +<body> + <iframe id='promptFrame' + src='http://{{hosts[alt][www]}}:{{ports[http][0]}}/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html' + onload='on_iframe_load()'></iframe> +</body> + +</html> diff --git a/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html new file mode 100644 index 00000000000..80b2c61b396 --- /dev/null +++ b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/confirm.html @@ -0,0 +1,11 @@ +<script> + function handleEvent(e) { + var conf = window.confirm('Confirm Dialog'); + if (conf == false) { + window.parent.postMessage('pass', '*'); + } else { + window.parent.postMessage('fail', '*'); + } + } + window.addEventListener('message', handleEvent); +</script> diff --git a/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html new file mode 100644 index 00000000000..db40774770c --- /dev/null +++ b/tests/wpt/web-platform-tests/html/webappapis/user-prompts/cannot-show-simple-dialogs/support/prompt.html @@ -0,0 +1,11 @@ +<script> + function handleEvent(e) { + var conf = window.prompt('Prompt Dialog'); + if (conf == null) { + window.parent.postMessage('pass', '*'); + } else { + window.parent.postMessage('fail', '*'); + } + } + window.addEventListener('message', handleEvent); +</script> diff --git a/tests/wpt/web-platform-tests/idle-detection/idlharness.https.window.js b/tests/wpt/web-platform-tests/idle-detection/idlharness.https.window.js index 98c590fb874..6bbdfe52877 100644 --- a/tests/wpt/web-platform-tests/idle-detection/idlharness.https.window.js +++ b/tests/wpt/web-platform-tests/idle-detection/idlharness.https.window.js @@ -8,7 +8,7 @@ 'use strict'; idl_test( - ['idle-detection.tentative'], + ['idle-detection'], ['dom', 'html'], async (idl_array, t) => { await test_driver.set_permission({ name: 'idle-detection' }, 'granted', false); diff --git a/tests/wpt/web-platform-tests/idle-detection/resources/idlharness-worker.js b/tests/wpt/web-platform-tests/idle-detection/resources/idlharness-worker.js index ba2ddfe169c..9733050284a 100644 --- a/tests/wpt/web-platform-tests/idle-detection/resources/idlharness-worker.js +++ b/tests/wpt/web-platform-tests/idle-detection/resources/idlharness-worker.js @@ -4,7 +4,7 @@ importScripts("/resources/testharness.js"); importScripts("/resources/WebIDLParser.js", "/resources/idlharness.js"); idl_test( - ['idle-detection.tentative'], + ['idle-detection'], ['dom', 'html'], async (idl_array, t) => { self.idle = new IdleDetector(); diff --git a/tests/wpt/web-platform-tests/infrastructure/testdriver/actions/actionsWithKeyPressed.html b/tests/wpt/web-platform-tests/infrastructure/testdriver/actions/actionsWithKeyPressed.html index b977f0c2873..3e0795b14a7 100644 --- a/tests/wpt/web-platform-tests/infrastructure/testdriver/actions/actionsWithKeyPressed.html +++ b/tests/wpt/web-platform-tests/infrastructure/testdriver/actions/actionsWithKeyPressed.html @@ -36,7 +36,7 @@ div#test2 { <script> let keys = []; -async_test(t => { +promise_test(async t => { let test1 = document.getElementById("test1"); let test2 = document.getElementById("test2"); document.getElementById("test1").addEventListener("click", @@ -60,8 +60,7 @@ async_test(t => { .pointerDown() .pointerUp(); - actions.send() - .then(t.step_func_done(() => assert_array_equals(keys, [true, true, false]))) - .catch(e => t.step_func(() => assert_unreached("Actions sequence failed " + e))); + await actions.send(); + assert_array_equals(keys, [true, true, false]); }); </script> diff --git a/tests/wpt/web-platform-tests/interfaces/content-index.idl b/tests/wpt/web-platform-tests/interfaces/content-index.idl index 4cd44f536d7..436f6d90e75 100644 --- a/tests/wpt/web-platform-tests/interfaces/content-index.idl +++ b/tests/wpt/web-platform-tests/interfaces/content-index.idl @@ -39,7 +39,8 @@ dictionary ContentIndexEventInit : ExtendableEventInit { required DOMString id; }; -[Constructor(DOMString type, ContentIndexEventInit init), Exposed=ServiceWorker] +[Exposed=ServiceWorker] interface ContentIndexEvent : ExtendableEvent { + constructor(DOMString type, ContentIndexEventInit init); readonly attribute DOMString id; }; diff --git a/tests/wpt/web-platform-tests/interfaces/cors-rfc1918.idl b/tests/wpt/web-platform-tests/interfaces/cors-rfc1918.idl index 083b6c3d630..d392a679ff2 100644 --- a/tests/wpt/web-platform-tests/interfaces/cors-rfc1918.idl +++ b/tests/wpt/web-platform-tests/interfaces/cors-rfc1918.idl @@ -1,7 +1,7 @@ // GENERATED CONTENT - DO NOT EDIT // Content was automatically extracted by Reffy into webref // (https://github.com/w3c/webref) -// Source: CORS and RFC1918 (https://wicg.github.io/cors-rfc1918/) +// Source: CORS and RFC1918 (https://wicg.github.io/private-network-access/) enum AddressSpace { "local", "private", "public" }; diff --git a/tests/wpt/web-platform-tests/interfaces/html.idl b/tests/wpt/web-platform-tests/interfaces/html.idl index 7f2eba2c7ae..08cc505da91 100644 --- a/tests/wpt/web-platform-tests/interfaces/html.idl +++ b/tests/wpt/web-platform-tests/interfaces/html.idl @@ -2027,48 +2027,6 @@ interface mixin NavigatorCookies { readonly attribute boolean cookieEnabled; }; -interface mixin NavigatorPlugins { - [SameObject] readonly attribute PluginArray plugins; - [SameObject] readonly attribute MimeTypeArray mimeTypes; - boolean javaEnabled(); -}; - -[Exposed=Window, - LegacyUnenumerableNamedProperties] -interface PluginArray { - undefined refresh(optional boolean reload = false); - readonly attribute unsigned long length; - getter Plugin? item(unsigned long index); - getter Plugin? namedItem(DOMString name); -}; - -[Exposed=Window, - LegacyUnenumerableNamedProperties] -interface MimeTypeArray { - readonly attribute unsigned long length; - getter MimeType? item(unsigned long index); - getter MimeType? namedItem(DOMString name); -}; - -[Exposed=Window, - LegacyUnenumerableNamedProperties] -interface Plugin { - readonly attribute DOMString name; - readonly attribute DOMString description; - readonly attribute DOMString filename; - readonly attribute unsigned long length; - getter MimeType? item(unsigned long index); - getter MimeType? namedItem(DOMString name); -}; - -[Exposed=Window] -interface MimeType { - readonly attribute DOMString type; - readonly attribute DOMString description; - readonly attribute DOMString suffixes; // comma-separated - readonly attribute Plugin enabledPlugin; -}; - [Exposed=(Window,Worker), Serializable, Transferable] interface ImageBitmap { readonly attribute unsigned long width; @@ -2674,3 +2632,42 @@ interface External { undefined AddSearchProvider(); undefined IsSearchProviderInstalled(); }; + +interface mixin NavigatorPlugins { + [SameObject] readonly attribute PluginArray plugins; + [SameObject] readonly attribute MimeTypeArray mimeTypes; + boolean javaEnabled(); +}; + +[Exposed=Window] +interface PluginArray { + undefined refresh(); + readonly attribute unsigned long length; + getter object? item(unsigned long index); + object? namedItem(DOMString name); +}; + +[Exposed=Window] +interface MimeTypeArray { + readonly attribute unsigned long length; + getter object? item(unsigned long index); + object? namedItem(DOMString name); +}; + +[Exposed=Window] +interface Plugin { + readonly attribute undefined name; + readonly attribute undefined description; + readonly attribute undefined filename; + readonly attribute undefined length; + getter undefined item(unsigned long index); + undefined namedItem(DOMString name); +}; + +[Exposed=Window] +interface MimeType { + readonly attribute undefined type; + readonly attribute undefined description; + readonly attribute undefined suffixes; + readonly attribute undefined enabledPlugin; +}; diff --git a/tests/wpt/web-platform-tests/interfaces/idle-detection.idl b/tests/wpt/web-platform-tests/interfaces/idle-detection.idl new file mode 100644 index 00000000000..54d42f3ca00 --- /dev/null +++ b/tests/wpt/web-platform-tests/interfaces/idle-detection.idl @@ -0,0 +1,31 @@ +// GENERATED CONTENT - DO NOT EDIT +// Content was automatically extracted by Reffy into webref +// (https://github.com/w3c/webref) +// Source: Idle Detection API (https://wicg.github.io/idle-detection/) + +enum UserIdleState { + "active", + "idle" +}; + +enum ScreenIdleState { + "locked", + "unlocked" +}; + +dictionary IdleOptions { + [EnforceRange] unsigned long long threshold; + AbortSignal signal; +}; + +[ + SecureContext, + Exposed=(Window,DedicatedWorker) +] interface IdleDetector : EventTarget { + constructor(); + readonly attribute UserIdleState? userState; + readonly attribute ScreenIdleState? screenState; + attribute EventHandler onchange; + [Exposed=Window] static Promise<PermissionState> requestPermission(); + Promise<undefined> start(optional IdleOptions options = {}); +}; diff --git a/tests/wpt/web-platform-tests/interfaces/idle-detection.tentative.idl b/tests/wpt/web-platform-tests/interfaces/idle-detection.tentative.idl deleted file mode 100644 index 865370a2dce..00000000000 --- a/tests/wpt/web-platform-tests/interfaces/idle-detection.tentative.idl +++ /dev/null @@ -1,26 +0,0 @@ -dictionary IdleOptions { - [EnforceRange] unsigned long threshold; - AbortSignal signal; -}; - -enum UserIdleState { - "active", - "idle" -}; - -enum ScreenIdleState { - "locked", - "unlocked" -}; - -[ - SecureContext, - Exposed=(Window,Worker) -] interface IdleDetector : EventTarget { - constructor(); - readonly attribute UserIdleState? userState; - readonly attribute ScreenIdleState? screenState; - attribute EventHandler onchange; - [Exposed=Window] static Promise<PermissionState> requestPermission(); - Promise<any> start(optional IdleOptions options = {}); -}; diff --git a/tests/wpt/web-platform-tests/interfaces/js-self-profiling.idl b/tests/wpt/web-platform-tests/interfaces/js-self-profiling.idl index 2c51c1cfe8e..a2199999ead 100644 --- a/tests/wpt/web-platform-tests/interfaces/js-self-profiling.idl +++ b/tests/wpt/web-platform-tests/interfaces/js-self-profiling.idl @@ -4,7 +4,7 @@ // Source: JS Self-Profiling API (https://wicg.github.io/js-self-profiling/) [Exposed=(Window,Worker)] -interface Profiler { +interface Profiler : EventTarget { readonly attribute DOMHighResTimeStamp sampleInterval; readonly attribute boolean stopped; diff --git a/tests/wpt/web-platform-tests/interfaces/media-capabilities.idl b/tests/wpt/web-platform-tests/interfaces/media-capabilities.idl index fb52b7c417c..128d01020a1 100644 --- a/tests/wpt/web-platform-tests/interfaces/media-capabilities.idl +++ b/tests/wpt/web-platform-tests/interfaces/media-capabilities.idl @@ -20,11 +20,12 @@ dictionary MediaEncodingConfiguration : MediaConfiguration { enum MediaDecodingType { "file", "media-source", + "webrtc" }; enum MediaEncodingType { "record", - "transmission" + "webrtc" }; dictionary VideoConfiguration { @@ -37,6 +38,7 @@ dictionary VideoConfiguration { HdrMetadataType hdrMetadataType; ColorGamut colorGamut; TransferFunction transferFunction; + DOMString scalabilityMode; }; enum HdrMetadataType { diff --git a/tests/wpt/web-platform-tests/interfaces/mediacapture-streams.idl b/tests/wpt/web-platform-tests/interfaces/mediacapture-streams.idl index 7cf6b5807a7..7d51a2af807 100644 --- a/tests/wpt/web-platform-tests/interfaces/mediacapture-streams.idl +++ b/tests/wpt/web-platform-tests/interfaces/mediacapture-streams.idl @@ -238,13 +238,3 @@ typedef (boolean or ConstrainBooleanParameters) ConstrainBoolean; typedef (DOMString or sequence<DOMString> or ConstrainDOMStringParameters) ConstrainDOMString; - -dictionary Capabilities {}; - -dictionary Settings {}; - -dictionary ConstraintSet {}; - -dictionary Constraints : ConstraintSet { - sequence<ConstraintSet> advanced; -}; diff --git a/tests/wpt/web-platform-tests/interfaces/permissions.idl b/tests/wpt/web-platform-tests/interfaces/permissions.idl index dfc934f9ce3..2f7fc1ed83d 100644 --- a/tests/wpt/web-platform-tests/interfaces/permissions.idl +++ b/tests/wpt/web-platform-tests/interfaces/permissions.idl @@ -34,6 +34,29 @@ interface Permissions { Promise<PermissionStatus> query(object permissionDesc); }; +enum PermissionName { + "geolocation", + "notifications", + "push", + "midi", + "camera", + "microphone", + "speaker-selection", + "device-info", + "background-fetch", + "background-sync", + "bluetooth", + "persistent-storage", + "ambient-light-sensor", + "accelerometer", + "gyroscope", + "magnetometer", + "clipboard-read", + "clipboard-write", + "display-capture", + "nfc", +}; + dictionary PushPermissionDescriptor : PermissionDescriptor { boolean userVisibleOnly = false; }; diff --git a/tests/wpt/web-platform-tests/interfaces/proximity.idl b/tests/wpt/web-platform-tests/interfaces/proximity.idl index 9ca71e01081..3cbfbd5ad13 100644 --- a/tests/wpt/web-platform-tests/interfaces/proximity.idl +++ b/tests/wpt/web-platform-tests/interfaces/proximity.idl @@ -3,8 +3,9 @@ // (https://github.com/w3c/webref) // Source: Proximity Sensor (https://w3c.github.io/proximity/) -[Constructor(optional SensorOptions sensorOptions = {}), SecureContext, Exposed=Window] +[SecureContext, Exposed=Window] interface ProximitySensor : Sensor { + constructor(optional SensorOptions sensorOptions = {}); readonly attribute double? distance; readonly attribute double? max; readonly attribute boolean? near; diff --git a/tests/wpt/web-platform-tests/interfaces/storage-buckets.tentative.idl b/tests/wpt/web-platform-tests/interfaces/storage-buckets.tentative.idl new file mode 100644 index 00000000000..153cd0d4b8e --- /dev/null +++ b/tests/wpt/web-platform-tests/interfaces/storage-buckets.tentative.idl @@ -0,0 +1,37 @@ +[ + Exposed=(Window,Worker), + SecureContext +] interface StorageBucketManager { + Promise<StorageBucket> open(DOMString name, + optional StorageBucketOptions options = {}); + Promise<sequence<DOMString>> keys(); + Promise<undefined> delete(DOMString name); +}; + +dictionary StorageBucketOptions { + DOMString? title = null; + boolean persisted = false; + StorageBucketDurability durability = "relaxed"; + unsigned long long? quota = null; + DOMTimeStamp? expires = null; +}; + +enum StorageBucketDurability { + "strict", + "relaxed" +}; + +[ + Exposed=(Window,Worker), + SecureContext +] interface StorageBucket { + [Exposed=Window] Promise<boolean> persist(); + Promise<boolean> persisted(); + + Promise<StorageEstimate> estimate(); + + Promise<StorageBucketDurability> durability(); + + Promise<undefined> setExpires(DOMTimeStamp expires); + Promise<DOMTimeStamp> expires(); +}; diff --git a/tests/wpt/web-platform-tests/intersection-observer/target-is-root.html b/tests/wpt/web-platform-tests/intersection-observer/target-is-root.html new file mode 100644 index 00000000000..1fe2ae6e174 --- /dev/null +++ b/tests/wpt/web-platform-tests/intersection-observer/target-is-root.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>IntersectionObserver when root == target doesn't compute an intersection</title> +<meta name="viewport" content="width=device-width,initial-scale=1"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1682915"> +<link rel="help" href="https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo"> +<!-- + Quoting IntersectionObserver section 3.2.8, "Run the Update Intersection Observations Steps", step 2, substep 3: + + If the intersection root is an Element, and target is not a descendant of + the intersection root in the containing block chain, skip to step 11. + +--> +<style> + #container { + overflow: scroll; + width: 100px; + height: 100px; + } +</style> +<div id=container> + <div></div> +</div> +<script> +async_test(function(t) { + let container = document.getElementById("container"); + let observer = new IntersectionObserver(t.step_func_done(function(entries) { + assert_equals(entries.length, 1); + assert_equals(entries[0].intersectionRatio, 0); + assert_equals(entries[0].isIntersecting, false); + }), { root: container }); + observer.observe(container); +}); +</script> diff --git a/tests/wpt/web-platform-tests/layout-instability/expand-above-viewport.html b/tests/wpt/web-platform-tests/layout-instability/expand-above-viewport.html new file mode 100644 index 00000000000..f1e3f704b73 --- /dev/null +++ b/tests/wpt/web-platform-tests/layout-instability/expand-above-viewport.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title>Layout Instability: layout shift when content expanded above the viewport</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-adapter.js"></script> +<script src="resources/util.js"></script> +<style> +body { + margin: 0; + /* To avoid browser automatic scroll offset adjustment for the expansion. */ + overflow-anchor: none; +} +</style> +<div id="expander" style="height: 50vh"></div> +<div id="shifted" style="width: 300px; height: 300vh; background: blue"></div> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + const viewHeight = document.documentElement.clientHeight; + window.scrollTo(0, viewHeight); + + await waitForAnimationFrames(2); + + // Expander expands to push |shifted| down. + expander.style.height = '150vh'; + + const expectedScore1 = computeExpectedScore(300 * viewHeight, viewHeight); + + // Observer fires after the frame is painted. + cls_expect(watcher, {score: 0}); + await watcher.promise; + cls_expect(watcher, {score: expectedScore1}); + + // Expander expands to push |shifted| out of viewport. + expander.style.height = '200vh'; + + const expectedScore2 = expectedScore1 + + computeExpectedScore(0.5 * 300 * viewHeight, 0.5 * viewHeight); + await watcher.promise; + cls_expect(watcher, {score: expectedScore2}); +}, "Layout shift when content expanded above the viewport"); + +</script> diff --git a/tests/wpt/web-platform-tests/layout-instability/shift-with-overflow-status-change.html b/tests/wpt/web-platform-tests/layout-instability/shift-with-overflow-status-change.html new file mode 100644 index 00000000000..33eea3080b4 --- /dev/null +++ b/tests/wpt/web-platform-tests/layout-instability/shift-with-overflow-status-change.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Layout Instability: change under overflow clipping container causing shift and overflow status change at the same time</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<div style="height: 100px"></div> +<div style="overflow: auto; width: 400px; height: 400px"> + <div id="resized" style="width: 600px; height: 100px; background: gray"></div> + <div id="shifted" style="width: 300px; height: 100px; background: blue"></div> +</div> +<script> +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + resized.style.width = '200px'; + resized.style.height = '200px'; + + const expectedScore = computeExpectedScore(300 * (100 + 100), 100); + + assert_equals(watcher.score, 0); + await watcher.promise; + assert_equals(watcher.score, expectedScore); + + resized.style.width = '600px'; + resized.style.height = '100px'; + await watcher.promise; + assert_equals(watcher.score, expectedScore * 2); +}, 'Change under overflow clipping container causing shift and overflow status change at the same time'); +</script> diff --git a/tests/wpt/web-platform-tests/layout-instability/transform-change.html b/tests/wpt/web-platform-tests/layout-instability/transform-change.html index fd9657a7145..3fa24964e11 100644 --- a/tests/wpt/web-platform-tests/layout-instability/transform-change.html +++ b/tests/wpt/web-platform-tests/layout-instability/transform-change.html @@ -1,5 +1,5 @@ <!DOCTYPE html> -<title>Layout Instability: transform change</title> +<title>Layout Instability: no layout shift for transform change</title> <link rel="help" href="https://wicg.github.io/layout-instability/" /> <style> body { margin: 0; } @@ -28,6 +28,6 @@ promise_test(async () => { await waitForAnimationFrames(2); // No shift should be reported. assert_equals(watcher.score, 0); -}, 'transform change'); +}, 'no layout shift for transform change'); </script> diff --git a/tests/wpt/web-platform-tests/layout-instability/transform-counter-layout-shift.html b/tests/wpt/web-platform-tests/layout-instability/transform-counter-layout-shift.html new file mode 100644 index 00000000000..fd744e383ba --- /dev/null +++ b/tests/wpt/web-platform-tests/layout-instability/transform-counter-layout-shift.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Layout Instability: no layout shift if transform change counters location change</title> +<link rel="help" href="https://wicg.github.io/layout-instability/" /> +<style> +body { margin: 0; } +#transformed { position: relative; transform: translateX(20px); width: 100px; height: 100px; } +#child { width: 400px; height: 400px; } +</style> +<div id="transformed"> + <div id="child"></div> +</div> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/util.js"></script> +<script> + +promise_test(async () => { + const watcher = new ScoreWatcher; + + // Wait for the initial render to complete. + await waitForAnimationFrames(2); + + // Modify the transform and the location at the same time, and the values + // cancel each other visually, for which no shift should be reported. + transformed.style.transform = 'translateY(100px)'; + transformed.style.top = '-100px'; + transformed.style.left = '20px'; + // Change size of child, for which no shift should be reported, either. + child.style.width = '300px'; + + await waitForAnimationFrames(2); + // No shift should be reported. + assert_equals(watcher.score, 0); +}, 'no layout shift if transform change counters location change'); + +</script> diff --git a/tests/wpt/web-platform-tests/lint.ignore b/tests/wpt/web-platform-tests/lint.ignore index 72e26ff7852..689b94586f4 100644 --- a/tests/wpt/web-platform-tests/lint.ignore +++ b/tests/wpt/web-platform-tests/lint.ignore @@ -56,6 +56,7 @@ W3C-TEST.ORG: .gitignore W3C-TEST.ORG: README.md W3C-TEST.ORG: */README.md W3C-TEST.ORG: docs/* +WEB-PLATFORM.TEST:*/README.md WEB-PLATFORM.TEST:docs/* CR AT EOL, INDENT TABS:docs/make.bat INDENT TABS:docs/Makefile @@ -94,7 +95,6 @@ TRAILING WHITESPACE: server-timing/resources/parsing/* TRAILING WHITESPACE: webvtt/parsing/file-parsing/support/*.vtt TRAILING WHITESPACE: webvtt/parsing/file-parsing/tests/support/*.vtt TRAILING WHITESPACE: xhr/resources/headers-some-are-empty.asis -TRAILING WHITESPACE: cookies/http-state/resources/test-files/* # Intentional use of print statements PRINT STATEMENT: dom/nodes/Document-createElement-namespace-tests/generate.py @@ -159,6 +159,7 @@ SET TIMEOUT: html/browsers/history/the-location-interface/* SET TIMEOUT: html/browsers/history/the-session-history-of-browsing-contexts/* SET TIMEOUT: html/browsers/offline/* SET TIMEOUT: html/browsers/the-window-object/* +SET TIMEOUT: html/cross-origin-embedder-policy/credentialless/resources/dispatcher.js SET TIMEOUT: html/editing/dnd/* SET TIMEOUT: html/semantics/embedded-content/the-iframe-element/* SET TIMEOUT: html/semantics/embedded-content/the-img-element/* @@ -595,15 +596,7 @@ CSS-COLLIDING-REF-NAME: css/CSS2/visuren/inline-formatting-context-001-ref.xht CSS-COLLIDING-REF-NAME: css/CSS2/linebox/inline-formatting-context-001-ref.xht CSS-COLLIDING-REF-NAME: css/css-flexbox/reference/percentage-size-subitems-001-ref.html CSS-COLLIDING-REF-NAME: css/css-grid/grid-items/percentage-size-subitems-001-ref.html -CSS-COLLIDING-REF-NAME: css/css-contain/reference/contain-size-button-001-ref.html -CSS-COLLIDING-REF-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-button-001-ref.html -CSS-COLLIDING-REF-NAME: css/css-contain/reference/contain-size-grid-001-ref.html -CSS-COLLIDING-REF-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-grid-001-ref.html CSS-COLLIDING-SUPPORT-NAME: css/css-backgrounds/support/red.png -CSS-COLLIDING-REF-NAME: css/css-contain/reference/contain-size-fieldset-001-ref.html -CSS-COLLIDING-REF-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-001-ref.html -CSS-COLLIDING-REF-NAME: css/css-contain/reference/contain-size-fieldset-002-ref.html -CSS-COLLIDING-REF-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/contain/contain-size-fieldset-002-ref.html CSS-COLLIDING-SUPPORT-NAME: css/compositing/mix-blend-mode/support/red.png CSS-COLLIDING-SUPPORT-NAME: css/compositing/background-blending/support/red.png CSS-COLLIDING-SUPPORT-NAME: css/CSS2/normal-flow/support/replaced-min-max-3.png diff --git a/tests/wpt/web-platform-tests/mathml/crashtests/multicol-inside-ms.html b/tests/wpt/web-platform-tests/mathml/crashtests/multicol-inside-ms.html new file mode 100644 index 00000000000..b7d481e1fb7 --- /dev/null +++ b/tests/wpt/web-platform-tests/mathml/crashtests/multicol-inside-ms.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org"> +<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1178979"> +<math><ms><span><span style="columns:3;"> diff --git a/tests/wpt/web-platform-tests/mediacapture-record/MediaRecorder-pause-resume.html b/tests/wpt/web-platform-tests/mediacapture-record/MediaRecorder-pause-resume.html index 39f98cc85a1..a1495dcb0c7 100644 --- a/tests/wpt/web-platform-tests/mediacapture-record/MediaRecorder-pause-resume.html +++ b/tests/wpt/web-platform-tests/mediacapture-record/MediaRecorder-pause-resume.html @@ -56,6 +56,34 @@ assert_array_equals(events, ["start", "pause", "resume", "dataavailable", "stop"], "Should have gotten expected events"); }, "MediaRecorder handles pause() and resume() calls appropriately in state and events"); + + promise_test(async () => { + let video = createVideoStream(); + let recorder = new MediaRecorder(video); + let events = recordEvents(recorder, + ["start", "stop", "dataavailable", "pause", "resume", "error"]); + + recorder.start(); + assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully"); + await new Promise(r => recorder.onstart = r); + + recorder.pause(); + assert_equals(recorder.state, "paused", "MediaRecorder should be paused immediately following pause()"); + let event = await new Promise(r => recorder.onpause = r); + assert_equals(event.type, "pause", "the event type should be pause"); + assert_true(event.isTrusted, "isTrusted should be true when the event is created by C++"); + + recorder.stop(); + assert_equals(recorder.state, "inactive", "MediaRecorder should be inactive after being stopped"); + await new Promise(r => recorder.onstop = r); + + recorder.start(); + assert_equals(recorder.state, "recording", "MediaRecorder has been started successfully"); + await new Promise(r => recorder.onstart = r); + + assert_array_equals(events, ["start", "pause", "dataavailable", "stop", "start"], + "Should have gotten expected events"); + }, "MediaRecorder handles stop() in paused state appropriately"); </script> </body> </html> diff --git a/tests/wpt/web-platform-tests/mixed-content/blob.https.sub.html b/tests/wpt/web-platform-tests/mixed-content/blob.https.sub.html new file mode 100644 index 00000000000..4e4bba6e0cf --- /dev/null +++ b/tests/wpt/web-platform-tests/mixed-content/blob.https.sub.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> + <head> + <title>Mixed-Content: blob tests</title> + <meta charset="utf-8"> + <meta name="description" content="Test a request to a blob: URL is mixed content if the blob's origin is not potentially trustworthy."> + <meta name="help" href="https://w3c.github.io/webappsec-mixed-content/#should-block-fetch"> + <meta name="help" href="https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-url"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + </head> + <body> + <script> + async function try_fetch_request(url) { + try { + const response = await fetch(url); + return response.ok; + } catch(e) { + return false; + } + } + + function try_script_load(url) { + return new Promise(resolve => { + let script = document.createElement("script"); + script.onload = () => resolve(true); + script.onerror = () => resolve(false); + script.src = url; + document.body.appendChild(script); + }); + } + + const popup_http = "http://{{domains[]}}:{{ports[http][0]}}/mixed-content/resources/blob-popup.html"; + const popup_https = "https://{{domains[]}}:{{ports[https][0]}}/mixed-content/resources/blob-popup.html"; + [popup_https, popup_http].forEach(popup_url => { + promise_test(t => { + return new Promise(resolve => { + window.addEventListener("message", resolve, {once: true}); + window.open(popup_url); + }).then(async function(event) { + let data = event.data; + assert_equals(await try_fetch_request(data.js_blob_url), + data.potentially_trustworthy, + "Fetch request"); + assert_equals(await try_script_load(data.js_blob_url), + data.potentially_trustworthy, + "Script load"); + event.source.close(); + }); + }); + }); + </script> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/mixed-content/resources/blob-popup.html b/tests/wpt/web-platform-tests/mixed-content/resources/blob-popup.html new file mode 100644 index 00000000000..bc3c97ef8dc --- /dev/null +++ b/tests/wpt/web-platform-tests/mixed-content/resources/blob-popup.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script> + opener.postMessage({ + potentially_trustworthy: location.protocol === "https:", + js_blob_url: URL.createObjectURL( + new Blob([`const message = "Hello World!"`], + {type: 'application/javascript'})), + }, "*"); +</script> diff --git a/tests/wpt/web-platform-tests/network-error-logging/sends-report-on-redirect.https.html b/tests/wpt/web-platform-tests/network-error-logging/sends-report-on-redirect.https.html index c9ba405f6fa..da6ae72c5a2 100644 --- a/tests/wpt/web-platform-tests/network-error-logging/sends-report-on-redirect.https.html +++ b/tests/wpt/web-platform-tests/network-error-logging/sends-report-on-redirect.https.html @@ -32,7 +32,7 @@ metadata: { content_type: "application/reports+json", }, - }), "receive report about redirected resource"); + }, true /* retain */), "receive report about redirected resource"); assert_true(await reportExists({ // This happens to be where we're redirected to. url: getURLForResourceWithNoPolicy(), diff --git a/tests/wpt/web-platform-tests/network-error-logging/support/nel.sub.js b/tests/wpt/web-platform-tests/network-error-logging/support/nel.sub.js index 51a01a082e8..856af10cf7f 100644 --- a/tests/wpt/web-platform-tests/network-error-logging/support/nel.sub.js +++ b/tests/wpt/web-platform-tests/network-error-logging/support/nel.sub.js @@ -247,12 +247,14 @@ function _isSubsetOf(obj1, obj2) { * expected. */ -async function reportExists(expected) { +async function reportExists(expected, retain_reports) { var timeout = document.querySelector("meta[name=timeout][content=long]") ? 50 : 1; var reportLocation = - "/network-error-logging/support/report.py?op=retrieve_report&timeout=" + + "/reporting/resources/report.py?op=retrieve_report&timeout=" + timeout + "&reportID=" + reportID; + if (retain_reports) + reportLocation += "&retain=1"; const response = await fetch(reportLocation); const json = await response.json(); for (const report of json) { @@ -268,11 +270,13 @@ async function reportExists(expected) { * expected. */ -async function reportsExist(expected_reports) { +async function reportsExist(expected_reports, retain_reports) { const timeout = 10; let reportLocation = - "/network-error-logging/support/report.py?op=retrieve_report&timeout=" + + "/reporting/resources/report.py?op=retrieve_report&timeout=" + timeout + "&reportID=" + reportID; + if (retain_reports) + reportLocation += "&retain"; // There must be the report of pass.png, so adding 1. const min_count = expected_reports.length + 1; reportLocation += "&min_count=" + min_count; diff --git a/tests/wpt/web-platform-tests/network-error-logging/support/pass.png.sub.headers b/tests/wpt/web-platform-tests/network-error-logging/support/pass.png.sub.headers index 15d246d8d9d..4cf91f1359a 100644 --- a/tests/wpt/web-platform-tests/network-error-logging/support/pass.png.sub.headers +++ b/tests/wpt/web-platform-tests/network-error-logging/support/pass.png.sub.headers @@ -2,5 +2,5 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache -Report-To: { "group": "nel-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/network-error-logging/support/report.py?op=put&reportID={{GET[id]}}" }] } +Report-To: { "group": "nel-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{GET[id]}}" }] } NEL: {"report_to": "nel-group", "max_age": 10886400, "success_fraction": {{GET[success_fraction]}}} diff --git a/tests/wpt/web-platform-tests/network-error-logging/support/report.py b/tests/wpt/web-platform-tests/network-error-logging/support/report.py deleted file mode 100644 index 65433e7840b..00000000000 --- a/tests/wpt/web-platform-tests/network-error-logging/support/report.py +++ /dev/null @@ -1,59 +0,0 @@ -import time -import json -import re - -from wptserve.utils import isomorphic_decode - -def retrieve_from_stash(request, key, timeout, min_count, default_value): - t0 = time.time() - while time.time() - t0 < timeout: - time.sleep(0.5) - value = request.server.stash.take(key=key) - if value is not None and len(value) >= min_count: - request.server.stash.put(key=key, value=value) - return json.dumps(value) - - return default_value - -def main(request, response): - # Handle CORS preflight requests - if request.method == u'OPTIONS': - # Always reject preflights for one subdomain - if b"www2" in request.headers[b"Origin"]: - return (400, [], u"CORS preflight rejected for www2") - return [ - (b"Content-Type", b"text/plain"), - (b"Access-Control-Allow-Origin", b"*"), - (b"Access-Control-Allow-Methods", b"post"), - (b"Access-Control-Allow-Headers", b"Content-Type"), - ], u"CORS allowed" - - op = request.GET.first(b"op"); - key = request.GET.first(b"reportID") - - if op == b"retrieve_report": - try: - timeout = float(request.GET.first(b"timeout")) - except: - timeout = 0.5 - try: - min_count = int(request.GET.first(b"min_count")) - except: - min_count = 1 - return [(b"Content-Type", b"application/json")], retrieve_from_stash(request, key, timeout, min_count, u'[]') - - # append new reports - new_reports = json.loads(request.body) - for report in new_reports: - report[u"metadata"] = { - u"content_type": isomorphic_decode(request.headers[b"Content-Type"]), - } - with request.server.stash.lock: - reports = request.server.stash.take(key=key) - if reports is None: - reports = [] - reports.extend(new_reports) - request.server.stash.put(key=key, value=reports) - - # return acknowledgement report - return [(b"Content-Type", b"text/plain")], u"Recorded report" diff --git a/tests/wpt/web-platform-tests/network-error-logging/support/subdomains-pass.png.sub.headers b/tests/wpt/web-platform-tests/network-error-logging/support/subdomains-pass.png.sub.headers index 50124b8cfcd..ae6ce3f43cb 100644 --- a/tests/wpt/web-platform-tests/network-error-logging/support/subdomains-pass.png.sub.headers +++ b/tests/wpt/web-platform-tests/network-error-logging/support/subdomains-pass.png.sub.headers @@ -2,5 +2,5 @@ Expires: Mon, 26 Jul 1997 05:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache -Report-To: { "group": "nel-group", "max_age": 10886400, "include_subdomains": true, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/network-error-logging/support/report.py?op=put&reportID={{GET[id]}}" }] } +Report-To: { "group": "nel-group", "max_age": 10886400, "include_subdomains": true, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{GET[id]}}" }] } NEL: {"report_to": "nel-group", "max_age": 10886400, "include_subdomains": true, "success_fraction": 1.0} diff --git a/tests/wpt/web-platform-tests/permissions-policy/permissions-policy-opaque-origin-history.https.html b/tests/wpt/web-platform-tests/permissions-policy/permissions-policy-opaque-origin-history.https.html new file mode 100644 index 00000000000..969ca369e1b --- /dev/null +++ b/tests/wpt/web-platform-tests/permissions-policy/permissions-policy-opaque-origin-history.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script> + + function get_response() { + return new Promise(resolve => { + window.addEventListener('message', e => { + resolve(e.data); + }, { once: true }); + }); + } + + promise_test(async () => { + // - opaque-origin-history1.html navigates itself to opaque-origin-history2.html. + // - opaque-origin-history2.html call window.history.back() to navigate + // back to opaque-origin-history1.html + // - opaque-origin-history1.html should still be able to access fullscreen + // feature after the history.back() navigation. + const iframe = document.createElement('iframe'); + // sandbox iframe so that it has opaque origin. + iframe.sandbox = 'allow-scripts'; + iframe.src = 'resources/opaque-origin-history1.sub.https.html'; + iframe.allow = "fullscreen 'src'"; + document.body.appendChild(iframe); + + + assert_equals( + await get_response(), + 'fullscreen enabled in opaque-origin-history1.html', + 'iframe should be able to access fullscreen.' + ); + + iframe.contentWindow.postMessage('redirect', '*'); + + assert_equals( + await get_response(), + 'fullscreen enabled in opaque-origin-history1.html', + 'iframe should still be able to access fullscreen after history.back() navigation.' + ); + }); + </script> +</body> diff --git a/tests/wpt/web-platform-tests/permissions-policy/permissions-policy-opaque-origin.https.html b/tests/wpt/web-platform-tests/permissions-policy/permissions-policy-opaque-origin.https.html new file mode 100644 index 00000000000..edb6e11a957 --- /dev/null +++ b/tests/wpt/web-platform-tests/permissions-policy/permissions-policy-opaque-origin.https.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script> + async_test(t => { + // opaque-origin1.html navigates itself to opaque-origin2.html onload. + // The 'src' shorthand in allow="fullscreen 'src'" should only match + // the initial src of opaque-origin1.html. opaque-origin2.html + // should not get access to fullscreen. + const iframe = document.createElement('iframe'); + iframe.sandbox = 'allow-scripts'; + iframe.src = 'resources/opaque-origin1.sub.https.html'; + iframe.allow = "fullscreen 'src'"; + + window.addEventListener('message', t.step_func_done(e => { + assert_equals(e.data, "fullscreen disabled in opaque-origin2.html"); + })); + + document.body.appendChild(iframe); + }); + </script> +</body> diff --git a/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin-history1.sub.https.html b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin-history1.sub.https.html new file mode 100644 index 00000000000..cb1f214f53c --- /dev/null +++ b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin-history1.sub.https.html @@ -0,0 +1,15 @@ +<script> + window.addEventListener('message', e => { + if (e.data == 'redirect') { + location.assign( + "https://{{domains[]}}:{{location[port]}}/permissions-policy/resources/opaque-origin-history2.https.html"); + } + }); + + parent.postMessage( + document.fullscreenEnabled ? + 'fullscreen enabled in opaque-origin-history1.html' : + 'fullscreen disabled in opaque-origin-history1.html', + '*' + ); +</script>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin-history2.https.html b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin-history2.https.html new file mode 100644 index 00000000000..20e63cf48b4 --- /dev/null +++ b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin-history2.https.html @@ -0,0 +1,3 @@ +<script> + history.back(); +</script>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin1.sub.https.html b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin1.sub.https.html new file mode 100644 index 00000000000..f8a8c9d1adf --- /dev/null +++ b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin1.sub.https.html @@ -0,0 +1,4 @@ +<script> + location.assign( + "https://{{domains[]}}:{{location[port]}}/permissions-policy/resources/opaque-origin2.https.html"); +</script>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin2.https.html b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin2.https.html new file mode 100644 index 00000000000..73122ff7f6c --- /dev/null +++ b/tests/wpt/web-platform-tests/permissions-policy/resources/opaque-origin2.https.html @@ -0,0 +1,8 @@ +<script> + parent.postMessage( + document.fullscreenEnabled ? + 'fullscreen enabled in opaque-origin2.html' : + 'fullscreen disabled in opaque-origin2.html', + '*' + ); +</script>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/pointerevents/idlharness.window.js b/tests/wpt/web-platform-tests/pointerevents/idlharness.window.js index b41a65f3ed0..e6e84fa9c72 100644 --- a/tests/wpt/web-platform-tests/pointerevents/idlharness.window.js +++ b/tests/wpt/web-platform-tests/pointerevents/idlharness.window.js @@ -12,7 +12,7 @@ idl_test( idl_array => { idl_array.add_objects({ Document: ['document'], - Element: ['document'], + Element: ['document.body'], Window: ['window'], Navigator: ['navigator'], PointerEvent: ['new PointerEvent("type")'] diff --git a/tests/wpt/web-platform-tests/pointerevents/pointerevent_attributes_hoverable_pointers.html b/tests/wpt/web-platform-tests/pointerevents/pointerevent_attributes_hoverable_pointers.html index 0449f92642f..6051991375e 100644 --- a/tests/wpt/web-platform-tests/pointerevents/pointerevent_attributes_hoverable_pointers.html +++ b/tests/wpt/web-platform-tests/pointerevents/pointerevent_attributes_hoverable_pointers.html @@ -3,6 +3,8 @@ <head> <title>Pointer Events properties tests</title> <meta name="viewport" content="width=device-width"> + <meta name="variant" content="?mouse"> + <meta name="variant" content="?pen"> <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> @@ -12,6 +14,7 @@ <!-- Additional helper script for common checks across event types --> <script type="text/javascript" src="pointerevent_support.js"></script> <script> + var input = location.search.substring(1); var detected_pointertypes = {}; var detected_eventTypes = {}; var eventList = ['pointerover', 'pointerenter', 'pointermove', 'pointerdown', 'pointerup', 'pointerout', 'pointerleave']; @@ -92,7 +95,7 @@ } function run() { - var test_pointerEvent = setup_pointerevent_test("pointerevent attributes", HOVERABLE_POINTERS); + var test_pointerEvent = setup_pointerevent_test("pointerevent attributes", [input]); var square1 = document.getElementById("square1"); var rectSquare1 = square1.getBoundingClientRect(); var innerFrame = document.getElementById('innerFrame'); @@ -124,23 +127,15 @@ }); }); - // Inject mouse and pen inputs. - actions_promise = clickInTarget("mouse", square1).then(function() { - return moveToDocument("mouse"); + // Inject mouse or pen inputs. + actions_promise = clickInTarget(input, square1).then(function() { + return moveToDocument(input); }).then(function() { - return clickInTarget("mouse", square2); + return clickInTarget(input, square2); }).then(function() { - return moveToDocument("mouse"); + return moveToDocument(input); }).then(function() { test_pointerEvent.done(); - }).then(function() { - return clickInTarget("pen", square1); - }).then(function() { - return moveToDocument("pen"); - }).then(function() { - return clickInTarget("pen", square2); - }).then(function() { - return moveToDocument("pen"); }); } </script> diff --git a/tests/wpt/web-platform-tests/portals/csp/frame-ancestors.sub.html b/tests/wpt/web-platform-tests/portals/csp/frame-ancestors.sub.html index 57f1eb3d88c..096ed00c7a8 100644 --- a/tests/wpt/web-platform-tests/portals/csp/frame-ancestors.sub.html +++ b/tests/wpt/web-platform-tests/portals/csp/frame-ancestors.sub.html @@ -7,7 +7,7 @@ <title>Blocked portals are reported correctly</title> </head> <body> - <portal src="/content-security-policy/frame-ancestors/support/content-security-policy.sub.html?policy=report-uri%20../../support/report.py%3Fop=put%26reportID={{$id:uuid()}}%3B%20frame-ancestors%20'none'"></portal> + <portal src="/content-security-policy/frame-ancestors/support/content-security-policy.sub.html?policy=report-uri%20/reporting/resources/report.py%3Fop=put%26reportID={{$id:uuid()}}%3B%20frame-ancestors%20'none'"></portal> <script async defer src="/content-security-policy/support/checkReport.sub.js?reportField=violated-directive&reportValue=frame-ancestors%20'none'&reportID={{$id}}"></script> </body> </html> diff --git a/tests/wpt/web-platform-tests/portals/portals-activate-empty-browsing-context.html b/tests/wpt/web-platform-tests/portals/portals-activate-empty-browsing-context.html index b1787782ffd..0c63e384977 100644 --- a/tests/wpt/web-platform-tests/portals/portals-activate-empty-browsing-context.html +++ b/tests/wpt/web-platform-tests/portals/portals-activate-empty-browsing-context.html @@ -19,7 +19,7 @@ promise_test(async t => { t.add_cleanup(() => { document.body.removeChild(portal); }); // We use a status of 204 (No Content) as that couldn't possibly mature. - portal.src = "resources/204-no-content.asis"; + portal.src = "/common/blank.html?pipe=status(204)" await promise_rejects_dom(t, 'InvalidStateError', portal.activate()); }, "A portal that has not completed an initial navigation cannot be activated"); </script> diff --git a/tests/wpt/web-platform-tests/portals/portals-focus.sub.html b/tests/wpt/web-platform-tests/portals/portals-focus.sub.html index 3e3045adf90..54b4312be0a 100644 --- a/tests/wpt/web-platform-tests/portals/portals-focus.sub.html +++ b/tests/wpt/web-platform-tests/portals/portals-focus.sub.html @@ -29,6 +29,19 @@ } }, "test that an element inside a portal cannot steal focus"); + promise_test(async () => { + let portal = await createPortal(document, new URL("resources/focus-page-with-button.html", location.href)); + try { + let activeElementUpdated = new Promise(r => { + portal.onmessage = e => r(e.data.activeElementUpdated) + }); + portal.postMessage('focus-update-active-element'); + assert_true(await activeElementUpdated); + } finally { + document.body.removeChild(portal); + } + }, "test that activeElement inside a portal is updated after focus() is called"); + promise_test(async t => { let portal = await createPortal(document, new URL("resources/focus-page-with-x-origin-iframe.sub.html", location.href)); try { @@ -42,6 +55,19 @@ } }, "test that an element inside a portal's x-origin subframe cannot steal focus"); + promise_test(async () => { + let portal = await createPortal(document, new URL("resources/focus-page-with-x-origin-iframe.sub.html", location.href)); + try { + portal.postMessage("focus-update-active-element"); + let {activeElementUpdated} = await new Promise(r => { + portal.onmessage = e => r(e.data); + }); + assert_true(activeElementUpdated); + } finally { + document.body.removeChild(portal); + } + }, "test that a portal's x-origin subframe becomes active element on focus"); + promise_test(async t => { let win = await openBlankPortalHost(); let doc = win.document; @@ -87,6 +113,28 @@ } }, "test that a x-origin iframe inside an adopted portal cannot steal focus"); + promise_test(async () => { + let win = await openBlankPortalHost(); + let doc = win.document; + try { + let portal = await createPortal(doc, new URL("resources/focus-page-with-autofocus.html", location.href)); + portal.postMessage('check-active-element'); + let result = await new Promise(r => { + portal.onmessage = e => r(e.data); + }); + assert_true(result, "autofocused element is active element"); + + await portal.activate(); + win.portalHost.postMessage('check-active-element'); + result = await new Promise(r => { + win.portalHost.onmessage = e => r(e.data) + }); + assert_true(result, "autofocused element is still active element"); + } finally { + win.close(); + } + }, "test that autofocus inside a portal works"); + const TAB = "\ue004"; // https://w3c.github.io/webdriver/#keyboard-actions const SPACE = " " const RETURN = "\r"; diff --git a/tests/wpt/web-platform-tests/portals/resources/204-no-content.asis b/tests/wpt/web-platform-tests/portals/resources/204-no-content.asis deleted file mode 100644 index 58e46abbc9e..00000000000 --- a/tests/wpt/web-platform-tests/portals/resources/204-no-content.asis +++ /dev/null @@ -1 +0,0 @@ -HTTP/1.1 204 No Content diff --git a/tests/wpt/web-platform-tests/portals/resources/focus-page-with-autofocus.html b/tests/wpt/web-platform-tests/portals/resources/focus-page-with-autofocus.html new file mode 100644 index 00000000000..d498ef63355 --- /dev/null +++ b/tests/wpt/web-platform-tests/portals/resources/focus-page-with-autofocus.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<body> + <button id="one">one</button> + <button id="two" autofocus>two</button> + <button id="three">three</button> + <script> + function messageHandler(e) { + if (e.data === 'check-active-element') { + window.requestAnimationFrame(() => { + let autofocusedButton = document.querySelector('#two'); + e.source.postMessage(document.activeElement === autofocusedButton); + }); + } + } + + window.portalHost.onmessage = messageHandler; + window.onportalactivate = e => { + let portal = e.adoptPredecessor(); + portal.onmessage = messageHandler; + document.body.appendChild(portal); + } + </script> +</body> diff --git a/tests/wpt/web-platform-tests/portals/resources/focus-page-with-button.html b/tests/wpt/web-platform-tests/portals/resources/focus-page-with-button.html index 06722196510..81ed5465ab1 100644 --- a/tests/wpt/web-platform-tests/portals/resources/focus-page-with-button.html +++ b/tests/wpt/web-platform-tests/portals/resources/focus-page-with-button.html @@ -7,6 +7,12 @@ button.onfocus = () => e.source.postMessage({focused: true}, {targetOrigin: "*"}); button.focus(); } + + if (e.data == "focus-update-active-element") { + let button = document.querySelector("button"); + button.focus(); + e.source.postMessage({activeElementUpdated: document.activeElement === button}, {targetOrigin: "*"}); + } } if (window.portalHost) diff --git a/tests/wpt/web-platform-tests/portals/resources/focus-page-with-x-origin-iframe.sub.html b/tests/wpt/web-platform-tests/portals/resources/focus-page-with-x-origin-iframe.sub.html index 9807898a354..df7974e75bb 100644 --- a/tests/wpt/web-platform-tests/portals/resources/focus-page-with-x-origin-iframe.sub.html +++ b/tests/wpt/web-platform-tests/portals/resources/focus-page-with-x-origin-iframe.sub.html @@ -1,10 +1,10 @@ <!DOCTYPE html> <body> <script> - function handleMessage(e) { - if (e.data == "focus") { + async function handleMessage(e) { + if (e.data == "focus" || e.data == "focus-update-active-element") { let iframe = document.querySelector("iframe"); - iframe.contentWindow.postMessage("focus", "*"); + iframe.contentWindow.postMessage(e.data, "*"); } } diff --git a/tests/wpt/web-platform-tests/referrer-policy/generic/inheritance/iframe-inheritance-document-write.html b/tests/wpt/web-platform-tests/referrer-policy/generic/inheritance/iframe-inheritance-document-write.html index cba71bb1d9f..c88586aaf6f 100644 --- a/tests/wpt/web-platform-tests/referrer-policy/generic/inheritance/iframe-inheritance-document-write.html +++ b/tests/wpt/web-platform-tests/referrer-policy/generic/inheritance/iframe-inheritance-document-write.html @@ -7,14 +7,28 @@ <meta name="referrer" content="origin"> <div id="log"></div> <script> -async_test(t => { - window.addEventListener("message", t.step_func_done(msg => { - assert_equals(msg.data.referrer, self.origin + "/"); - })); + let reportedReferrer = () => { + return new Promise(resolve => { + window.addEventListener("message", msg => resolve(msg.data.referrer)); + }); + }; const iframe = document.createElement("iframe"); - document.body.appendChild(iframe); - iframe.contentDocument.write(createScriptString(get_host_info().REMOTE_ORIGIN)); - iframe.contentDocument.close(); -}); + promise_test(async t => { + let referrer_of_srcdoc_iframe = reportedReferrer(); + const script_to_fetch_cross_origin_resource = + createScriptString(get_host_info().REMOTE_ORIGIN, location.origin + "/custom"); + iframe.srcdoc = `<head><meta name="referrer" content="unsafe-url"></head>` + + script_to_fetch_cross_origin_resource; + document.body.appendChild(iframe); + assert_equals(await referrer_of_srcdoc_iframe, self.origin + "/custom", + "Srcdoc iframe setting referrer policy via meta header should use that referrer policy."); + + let referrer_after_document_open = reportedReferrer(); + iframe.contentDocument.open(); + iframe.contentDocument.write(script_to_fetch_cross_origin_resource); + iframe.contentDocument.close(); + assert_equals(await referrer_after_document_open, self.origin + "/custom", + "Referrer policy should not change after document.open()."); + }, "document.open() should not change the referrer policy of the opened document."); </script> diff --git a/tests/wpt/web-platform-tests/reporting/path-absolute-endpoint.https.sub.html.sub.headers b/tests/wpt/web-platform-tests/reporting/path-absolute-endpoint.https.sub.html.sub.headers index ec25b289449..5bd5ae7f0f5 100644 --- a/tests/wpt/web-platform-tests/reporting/path-absolute-endpoint.https.sub.html.sub.headers +++ b/tests/wpt/web-platform-tests/reporting/path-absolute-endpoint.https.sub.html.sub.headers @@ -1,2 +1,2 @@ -Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "/reporting/resources/report.py?id=d0d517bf-891b-457a-b970-8b2b2c81a0bf" }] } +Report-To: { "group": "csp-group", "max_age": 10886400, "endpoints": [{ "url": "/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf" }] } Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group diff --git a/tests/wpt/web-platform-tests/reporting/resources/README.md b/tests/wpt/web-platform-tests/reporting/resources/README.md new file mode 100644 index 00000000000..f3ee06c14a4 --- /dev/null +++ b/tests/wpt/web-platform-tests/reporting/resources/README.md @@ -0,0 +1,44 @@ +# Using the common report collector + +To send reports to the collector, configure the reporting API to POST reports +to the collector's URL. This can be same- or cross- origin with the reporting +document, as the collector will follow the CORS protocol. + +The collector supports both CSP Level 2 (report-uri) reports as well as +Reporting API reports. + +A GET request can be used to retrieve stored reports for analysis. + +Sent credentials are stored with the reports, and can be retrieved separately. + +CORS Notes: +* Preflight requests originating from www2.web-platform.test will be rejected. + This allows tests to ensure that cross-origin report uploads are not sent when + the endpoint does not support CORS. + +Supported GET parameters: + `op`: For GET requests, a string indicating the operation to perform (see + below for description of supported operations). Defaults to + `retrieve_report`. + `reportID`: A UUID to associate with the reports sent from this document. This + can be used to distinguish between reports from multiple documents, and to + provide multiple distinct endpoints for a single document. Either `reportID` + or `endpoint` must be provided. + `endpoint`: A string which will be used to generate a UUID to be used as the + reportID. Either `reportID` or `endpoint` must be provided. + `timeout`: The amount of time to wait, in seconds, before responding. Defaults + to 0.5s. + `min_count`: The minimum number of reports to return with the `retrieve_report` + operation. If there have been fewer than this many reports received, then an + empty report list will be returned instead. + `retain`: If present, reports will remain in the stash after being retrieved. + By default, reports are cleared once retrieved. + +Operations: + `retrieve_report`: Returns all reports received so far for this reportID, as a + JSON-formatted list. If no reports have been received, an empty list will be + returned. + `retrieve_cookies`: Returns the cookies sent with the most recent reports for + this reportID, as a JSON-formatted object. + `retrieve_count`: Returns the number of POST requests for reports with this + reportID so far. diff --git a/tests/wpt/web-platform-tests/reporting/resources/report-helper.js b/tests/wpt/web-platform-tests/reporting/resources/report-helper.js index a20a9cd3811..181d1970b18 100644 --- a/tests/wpt/web-platform-tests/reporting/resources/report-helper.js +++ b/tests/wpt/web-platform-tests/reporting/resources/report-helper.js @@ -3,7 +3,7 @@ function wait(ms) { } async function pollReports(endpoint, id) { - const res = await fetch(`${endpoint}?id=${id}`, {cache: 'no-store'}); + const res = await fetch(`${endpoint}?reportID=${id}`, {cache: 'no-store'}); const reports = []; if (res.status === 200) { for (const report of await res.json()) { diff --git a/tests/wpt/web-platform-tests/reporting/resources/report.py b/tests/wpt/web-platform-tests/reporting/resources/report.py index a3a0ee5ddba..3adbf3668e5 100644 --- a/tests/wpt/web-platform-tests/reporting/resources/report.py +++ b/tests/wpt/web-platform-tests/reporting/resources/report.py @@ -1,17 +1,131 @@ +import time import json +import re +import uuid + +from wptserve.utils import isomorphic_decode + +def retrieve_from_stash(request, key, timeout, default_value, min_count=None, retain=False): + """Retrieve the set of reports for a given report ID. + + This will extract either the set of reports, credentials, or request count + from the stash (depending on the key passed in) and return it encoded as JSON. + + When retrieving reports, this will not return any reports until min_count + reports have been received. + + If timeout seconds elapse before the requested data can be found in the stash, + or before at least min_count reports are received, default_value will be + returned instead.""" + t0 = time.time() + while time.time() - t0 < timeout: + time.sleep(0.5) + with request.server.stash.lock: + value = request.server.stash.take(key=key) + if value is not None: + have_sufficient_reports = (min_count is None or len(value) >= min_count) + if retain or not have_sufficient_reports: + request.server.stash.put(key=key, value=value) + if have_sufficient_reports: + # If the last report received looks like a CSP report-uri report, then + # extract it from the list and return it alone. (This is until the CSP + # tests are modified to expect a list of reports returned in all cases.) + if isinstance(value,list) and 'csp-report' in value[-1]: + value = value[-1] + return json.dumps(value) + + return default_value def main(request, response): - key = request.GET.first(b'id') - - # No CORS support for cross-origin reporting endpoints - if request.method == u'POST': - reports = request.server.stash.take(key) or [] - for report in json.loads(request.body): - reports.append(report) - request.server.stash.put(key, reports) - return b'done' + # Handle CORS preflight requests + if request.method == u'OPTIONS': + # Always reject preflights for one subdomain + if b"www2" in request.headers[b"Origin"]: + return (400, [], u"CORS preflight rejected for www2") + return [ + (b"Content-Type", b"text/plain"), + (b"Access-Control-Allow-Origin", b"*"), + (b"Access-Control-Allow-Methods", b"post"), + (b"Access-Control-Allow-Headers", b"Content-Type"), + ], u"CORS allowed" + + if b"reportID" in request.GET: + key = request.GET.first(b"reportID") + elif b"endpoint" in request.GET: + key = uuid.uuid5(uuid.NAMESPACE_OID, isomorphic_decode(request.GET[b'endpoint'])).urn.encode('ascii')[9:] + else: + response.status = 400 + return "Either reportID or endpoint parameter is required." + + # Cookie and count keys are derived from the report ID. + cookie_key = re.sub(b'^....', b'cccc', key) + count_key = re.sub(b'^....', b'dddd', key) + if request.method == u'GET': - return json.dumps(request.server.stash.take(key) or []) + try: + timeout = float(request.GET.first(b"timeout")) + except: + timeout = 0.5 + try: + min_count = int(request.GET.first(b"min_count")) + except: + min_count = 1 + retain = (b"retain" in request.GET) + + op = request.GET.first(b"op", b"") + if op in (b"retrieve_report", b""): + return [(b"Content-Type", b"application/json")], retrieve_from_stash(request, key, timeout, u'[]', min_count, retain) + + if op == b"retrieve_cookies": + return [(b"Content-Type", b"application/json")], u"{ \"reportCookies\" : " + str(retrieve_from_stash(request, cookie_key, timeout, u"\"None\"")) + u"}" + + if op == b"retrieve_count": + return [(b"Content-Type", b"application/json")], json.dumps({u'report_count': str(retrieve_from_stash(request, count_key, timeout, 0))}) + + response.status = 400 + return "op parameter value not recognized." + + # Save cookies. + if len(request.cookies.keys()) > 0: + # Convert everything into strings and dump it into a dict. + temp_cookies_dict = {} + for dict_key in request.cookies.keys(): + temp_cookies_dict[isomorphic_decode(dict_key)] = str(request.cookies.get_list(dict_key)) + with request.server.stash.lock: + # Clear any existing cookie data for this request before storing new data. + request.server.stash.take(key=cookie_key) + request.server.stash.put(key=cookie_key, value=temp_cookies_dict) + + # Append new report(s). + new_reports = json.loads(request.body) + + # If the incoming report is a CSP report-uri report, then it will be a single + # dictionary rather than a list of reports. To handle this case, ensure that + # any non-list request bodies are wrapped in a list. + if not isinstance(new_reports, list): + new_reports = [new_reports] + + for report in new_reports: + report[u"metadata"] = { + u"content_type": isomorphic_decode(request.headers[b"Content-Type"]), + } + + with request.server.stash.lock: + reports = request.server.stash.take(key=key) + if reports is None: + reports = [] + reports.extend(new_reports) + request.server.stash.put(key=key, value=reports) + + # Increment report submission count. This tracks the number of times this + # reporting endpoint was contacted, rather than the total number of reports + # submitted, which can be seen from the length of the report list. + with request.server.stash.lock: + count = request.server.stash.take(key=count_key) + if count is None: + count = 0 + count += 1 + request.server.stash.put(key=count_key, value=count) - response.status = 400 - return b'invalid method' + # Return acknowledgement report. + return [(b"Content-Type", b"text/plain")], b"Recorded report " + request.body diff --git a/tests/wpt/web-platform-tests/resource-timing/CodingConventions.md b/tests/wpt/web-platform-tests/resource-timing/CodingConventions.md new file mode 100644 index 00000000000..461cae421e6 --- /dev/null +++ b/tests/wpt/web-platform-tests/resource-timing/CodingConventions.md @@ -0,0 +1,59 @@ +For [Resource Timing][1] tests, we want to have a consistent and clear coding +style. The goals of this style are to: +* Make it easier for new contributors to find their way around +* Help improve readability and maintainability +* Help us understand which parts of the spec are tested or not +Lots of the following rules are arbitrary but the value is realized in +consistency instead of adhering to the 'perfect' style. + +We want the test suite to be navigable. Developers should be able to easily +find the file or test that is relevant to their work. +* Tests should be arranged in files according to which piece of the spec they + test +* Files should be named using a consistent pattern +* HTML files should include useful meta tags + * `<title>` for controlling labels in results pages + * `<link rel="help">` to point at the relevant piece of the spec + +We want the test suite to run consistently. Flaky tests are counterproductive. +* Prefer `promise_test` to `async_test` + * Note that there’s [still potential for some concurrency][2]; use + `add_cleanup()` if needed + +We want the tests to be readable. Tests should be written in a modern style +with recurring patterns. +* 80 character line limits where we can +* Consistent use of anonymous functions + * prefer + ``` + fn(param => { + body(); + }); + ``` + + over + + ``` + fn(function(param) { + body(); + }); + ``` + +* Prefer `const` (or, if needed, `let`) to `var` +* Contain use of ‘.sub’ in filenames to known helper utilities where possible + * E.g. prefer use of get-host-info.sub.js to `{{host}}` or `{{ports[0]}}` + expressions +* Avoid use of webperftestharness[extension].js as it’s a layer of cognitive + overhead between test content and test intent + * Helper .js files are still encouraged where it makes sense but we want + to avoid a testing framework that is specific to Resource Timing (or + web performance APIs, in general). +* Prefer [`fetch_tests_from_window`][3] to collect test results from embedded + iframes instead of hand-rolled `postMessage` approaches +* Where possible, we want tests to be scalable - adding another test case + should be as simple as calling the tests with new parameters, rather than + copying an existing test and modifying it. + +[1]: https://www.w3.org/TR/resource-timing-2/ +[2]: https://web-platform-tests.org/writing-tests/testharness-api.html#promise-tests +[3]: https://web-platform-tests.org/writing-tests/testharness-api.html#consolidating-tests-from-other-documents diff --git a/tests/wpt/web-platform-tests/resource-timing/entry-attributes.html b/tests/wpt/web-platform-tests/resource-timing/entry-attributes.html new file mode 100644 index 00000000000..78d05107162 --- /dev/null +++ b/tests/wpt/web-platform-tests/resource-timing/entry-attributes.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8" /> +<title>Resource Timing: PerformanceResourceTiming attributes</title> +<link rel="help" href="https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +// Returns a promise that settles once the given path has been fetched. +function load_image(path) { + return new Promise(resolve => { + const img = new Image(); + img.onload = img.onerror = resolve; + img.src = path; + }); +} + +promise_test(async () => { + // Clean up entries from scripts includes. + performance.clearResourceTimings(); + await load_image("resources/fake_responses.py#hash=1"); + const entry_list = performance.getEntriesByType("resource"); + if (entry_list.length != 1) { + throw new Error("There should be one entry for one resource"); + } + assert_true(entry_list[0].name.includes('#hash=1'), + "There should be a hash in the resource name"); +}, "URL fragments should be present in the 'name' attribute"); + +</script> +</head> +<body> +<h1>Description</h1> +<p>This test validates that PerformanceResourceTiming entries' attributes are +populated with the correct values.</p> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/resource-timing/resource_hash.htm b/tests/wpt/web-platform-tests/resource-timing/resource_hash.htm deleted file mode 100644 index a44c1c90659..00000000000 --- a/tests/wpt/web-platform-tests/resource-timing/resource_hash.htm +++ /dev/null @@ -1,44 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<meta charset="utf-8" /> -<title>Resource Timing: image with a hash</title> -<link rel="help" href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-initiatortype"/> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script src="resources/webperftestharness.js"></script> -<script src="resources/webperftestharnessextension.js"></script> -<script> -setup({explicit_done: true}); - -// explicitly test the namespace before we start testing -test_namespace("getEntriesByType"); - -function onload_test() { - if (window.performance.getEntriesByType === undefined) { - done(); - return; - } - - const url = "resources/fake_responses.py?tag=" + Math.random() + '#hash=1'; - const image = new Image(); - image.onload = image.onerror = () => { - const entries = window.performance.getEntriesByType('resource').filter(r => r.initiatorType === 'img'); - test_equals(entries.length, 1, "There should be one entry"); - if (entries.length === 1) { - test_true(entries[0].name.indexOf('#hash=1') > -1, "There should be a hash in the resource name"); - } - done(); - - } - image.src = url; -} -window.onload = onload_test; -</script> -</head> -<body> -<h1>Description</h1> -<p>This test validates that a hash in an image URL is preserved in resource timing API results.</p> -<div id="log"></div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js b/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js index 2846adda6ab..f7e9cbd2502 100644 --- a/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js +++ b/tests/wpt/web-platform-tests/resources/chromium/webxr-test.js @@ -337,6 +337,7 @@ class MockRuntime { 'dom-overlay': vrMojom.XRSessionFeature.DOM_OVERLAY, 'light-estimation': vrMojom.XRSessionFeature.LIGHT_ESTIMATION, 'anchors': vrMojom.XRSessionFeature.ANCHORS, + 'depth-sensing': vrMojom.XRSessionFeature.DEPTH, }; static sessionModeToMojoMap = { @@ -387,6 +388,9 @@ class MockRuntime { // Anchor creation callback (initially null, can be set by tests). this.anchor_creation_callback_ = null; + this.depthSensingData_ = null; + this.depthSensingDataDirty_ = false; + let supportedModes = []; if (fakeDeviceInit.supportedModes) { supportedModes = fakeDeviceInit.supportedModes.slice(); @@ -433,6 +437,10 @@ class MockRuntime { this.world_ = fakeDeviceInit.world; } + if (fakeDeviceInit.depthSensingData) { + this.setDepthSensingData(fakeDeviceInit.depthSensingData); + } + this.defaultFramebufferScale_ = default_framebuffer_scale; this.enviromentBlendMode_ = this._convertBlendModeToEnum(fakeDeviceInit.environmentBlendMode); this.interactionMode_ = this._convertInteractionModeToEnum(fakeDeviceInit.interactionMode); @@ -613,6 +621,94 @@ class MockRuntime { this.hit_test_source_creation_callback_ = callback; } + setLightEstimate(fakeXrLightEstimateInit) { + if (!fakeXrLightEstimateInit.sphericalHarmonicsCoefficients) { + throw new TypeError("sphericalHarmonicsCoefficients must be set"); + } + + if (fakeXrLightEstimateInit.sphericalHarmonicsCoefficients.length != 27) { + throw new TypeError("Must supply all 27 sphericalHarmonicsCoefficients"); + } + + if (fakeXrLightEstimateInit.primaryLightDirection && fakeXrLightEstimateInit.primaryLightDirection.w != 0) { + throw new TypeError("W component of primaryLightDirection must be 0"); + } + + if (fakeXrLightEstimateInit.primaryLightIntensity && fakeXrLightEstimateInit.primaryLightIntensity.w != 1) { + throw new TypeError("W component of primaryLightIntensity must be 1"); + } + + // If the primaryLightDirection or primaryLightIntensity aren't set, we need to set them + // to the defaults that the spec expects. ArCore will either give us everything or nothing, + // so these aren't nullable on the mojom. + if (!fakeXrLightEstimateInit.primaryLightDirection) { + fakeXrLightEstimateInit.primaryLightDirection = { x: 0.0, y: 1.0, z: 0.0, w: 0.0 }; + } + + if (!fakeXrLightEstimateInit.primaryLightIntensity) { + fakeXrLightEstimateInit.primaryLightIntensity = { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }; + } + + let c = fakeXrLightEstimateInit.sphericalHarmonicsCoefficients; + + this.light_estimate_ = { + lightProbe: { + // XRSphereicalHarmonics + sphericalHarmonics: { + coefficients: [ + { red: c[0], green: c[1], blue: c[2] }, + { red: c[3], green: c[4], blue: c[5] }, + { red: c[6], green: c[7], blue: c[8] }, + { red: c[9], green: c[10], blue: c[11] }, + { red: c[12], green: c[13], blue: c[14] }, + { red: c[15], green: c[16], blue: c[17] }, + { red: c[18], green: c[19], blue: c[20] }, + { red: c[21], green: c[22], blue: c[23] }, + { red: c[24], green: c[25], blue: c[26] } + ] + }, + // Vector3dF + mainLightDirection: { + x: fakeXrLightEstimateInit.primaryLightDirection.x, + y: fakeXrLightEstimateInit.primaryLightDirection.y, + z: fakeXrLightEstimateInit.primaryLightDirection.z + }, + // RgbTupleF32 + mainLightIntensity: { + red: fakeXrLightEstimateInit.primaryLightIntensity.x, + green: fakeXrLightEstimateInit.primaryLightIntensity.y, + blue: fakeXrLightEstimateInit.primaryLightIntensity.z + } + } + } + } + + setDepthSensingData(depthSensingData) { + for(const key of ["depthData", "normDepthBufferFromNormView", "rawValueToMeters", "width", "height"]) { + if(!(key in depthSensingData)) { + throw new TypeError("Required key not present. Key: " + key); + } + } + + if(depthSensingData.depthData != null) { + // Create new object w/ properties based on the depthSensingData, but + // convert the FakeXRRigidTransformInit into a transformation matrix object. + this.depthSensingData_ = Object.assign({}, + depthSensingData, { + normDepthBufferFromNormView: composeGFXTransform(depthSensingData.normDepthBufferFromNormView), + }); + } else { + throw new TypeError("`depthData` is not set"); + } + + this.depthSensingDataDirty_ = true; + } + + clearDepthSensingData() { + this.depthSensingData_ = null; + this.depthSensingDataDirty_ = true; + } + // Helper methods getNonImmersiveDisplayInfo() { const displayInfo = this.getImmersiveDisplayInfo(); @@ -786,6 +882,7 @@ class MockRuntime { renderingTimeRatio: 0, stageParameters: this.stageParameters_, stageParametersId: this.stageParametersId_, + lightEstimationData: this.light_estimate_ }; this.next_frame_id_++; @@ -794,6 +891,8 @@ class MockRuntime { this._calculateAnchorInformation(frameData); + this._calculateDepthInformation(frameData); + this._injectAdditionalFrameData(options, frameData); resolve({frameData}); @@ -1056,6 +1155,8 @@ class MockRuntime { } } + this.enabledFeatures_ = enabled_features; + return Promise.resolve({ session: { submitFrameSink: submit_frame_sink, @@ -1066,7 +1167,12 @@ class MockRuntime { deviceConfig: { usesInputEventing: false, defaultFramebufferScale: this.defaultFramebufferScale_, - supportsViewportScaling: true + supportsViewportScaling: true, + depthConfiguration: + enabled_features.includes(vrMojom.XRSessionFeature.DEPTH) ? { + depthUsage: vrMojom.XRDepthUsage.kCPUOptimized, + depthDataFormat: vrMojom.XRDepthDataFormat.kLuminanceAlpha, + } : null, }, enviromentBlendMode: this.enviromentBlendMode_, interactionMode: this.interactionMode_ @@ -1079,8 +1185,16 @@ class MockRuntime { } runtimeSupportsSession(options) { + let result = this.supportedModes_.includes(options.mode); + + if (options.requiredFeatures.includes(vrMojom.XRSessionFeature.DEPTH) + || options.optionalFeatures.includes(vrMojom.XRSessionFeature.DEPTH)) { + result &= options.depthOptions.usagePreferences.includes(vrMojom.XRDepthUsage.kCPUOptimized); + result &= options.depthOptions.dataFormatPreferences.includes(vrMojom.XRDepthDataFormat.kLuminanceAlpha); + } + return Promise.resolve({ - supportsSession: this.supportedModes_.includes(options.mode) + supportsSession: result, }); } @@ -1136,6 +1250,43 @@ class MockRuntime { } } + // Private functions - depth sensing implementation: + + // Modifies passed in frameData to add anchor information. + _calculateDepthInformation(frameData) { + if (!this.supportedModes_.includes(vrMojom.XRSessionMode.kImmersiveAr)) { + return; + } + + if (!this.enabledFeatures_.includes(vrMojom.XRSessionFeature.DEPTH)) { + return; + } + + // If we don't have a current depth data, we'll return null + // (i.e. no data is not a valid data, so it cannot be "StillValid"). + if (this.depthSensingData_ == null) { + frameData.depthData = null; + return; + } + + if(!this.depthSensingDataDirty_) { + frameData.depthData = { dataStillValid: {}}; + return; + } + + frameData.depthData = { + updatedDepthData: { + timeDelta: frameData.timeDelta, + normTextureFromNormView: this.depthSensingData_.normDepthBufferFromNormView, + rawValueToMeters: this.depthSensingData_.rawValueToMeters, + size: { width: this.depthSensingData_.width, height: this.depthSensingData_.height }, + pixelData: { bytes: this.depthSensingData_.depthData } + } + }; + + this.depthSensingDataDirty_ = false; + } + // Private functions - hit test implementation: // Returns a Promise<bool> that signifies whether hit test source creation should succeed. @@ -1243,7 +1394,10 @@ class MockRuntime { result = result.concat(partial_result); } - return result.sort((lhs, rhs) => lhs.distance - rhs.distance); + return result.sort((lhs, rhs) => lhs.distance - rhs.distance).map((hitTest) => { + delete hitTest.distance; + return hitTest; + }); } // Hit tests the passed in ray (expressed as origin and direction) against world region. @@ -1337,6 +1491,7 @@ class MockRuntime { z_axis = neg(sub(direction, mul(cos_direction_and_y_axis, y_axis))); // Z should point towards the ray origin, not away. } + z_axis = normalize(z_axis); const x_axis = normalize(cross(y_axis, z_axis)); // Filter out the points not in polygon. diff --git a/tests/wpt/web-platform-tests/resources/test/conftest.py b/tests/wpt/web-platform-tests/resources/test/conftest.py index e46f93bc00f..860784d2409 100644 --- a/tests/wpt/web-platform-tests/resources/test/conftest.py +++ b/tests/wpt/web-platform-tests/resources/test/conftest.py @@ -19,6 +19,9 @@ HARNESS = os.path.join(HERE, 'harness.html') TEST_TYPES = ('functional', 'unit') DEFAULT_VARIANTS = ["?default"] +sys.path.insert(0, os.path.normpath(os.path.join(WPT_ROOT, "tools"))) +import localpaths + sys.path.insert(0, os.path.normpath(os.path.join(WPT_ROOT, "tools", "webdriver"))) import webdriver diff --git a/tests/wpt/web-platform-tests/resources/testharness.js b/tests/wpt/web-platform-tests/resources/testharness.js index adfb692ccd9..c62f0917c17 100644 --- a/tests/wpt/web-platform-tests/resources/testharness.js +++ b/tests/wpt/web-platform-tests/resources/testharness.js @@ -1187,7 +1187,7 @@ policies and contribution forms [3]. let stack = null; try { if (settings.debug) { - console.debug("ASSERT", name, tests.current_test.name, args); + console.debug("ASSERT", name, tests.current_test && tests.current_test.name, args); } if (tests.output) { tests.set_assert(name, ...args); @@ -2839,6 +2839,8 @@ policies and contribution forms [3]. this.hide_test_state = value; } else if (p == "output") { this.output = value; + } else if (p === "debug") { + settings.debug = value; } } } @@ -2863,7 +2865,7 @@ policies and contribution forms [3]. this.wait_for_finish = true; this.file_is_test = true; // Create the test, which will add it to the list of tests - async_test(); + tests.current_test = async_test(); }; Tests.prototype.set_status = function(status, message, stack) diff --git a/tests/wpt/web-platform-tests/sanitizer-api/idlharness.tentative.window.js b/tests/wpt/web-platform-tests/sanitizer-api/idlharness.https.tentative.window.js index 384317b8e55..384317b8e55 100644 --- a/tests/wpt/web-platform-tests/sanitizer-api/idlharness.tentative.window.js +++ b/tests/wpt/web-platform-tests/sanitizer-api/idlharness.https.tentative.window.js diff --git a/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-config.tentative.html b/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-config.https.tentative.html index e4dc40a8c63..e4dc40a8c63 100644 --- a/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-config.tentative.html +++ b/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-config.https.tentative.html diff --git a/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitize.tentative.html b/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitize.https.tentative.html index 32b7424a225..32b7424a225 100644 --- a/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitize.tentative.html +++ b/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitize.https.tentative.html diff --git a/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitizeToString.tentative.html b/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitizeToString.https.tentative.html index bb805a4c919..bb805a4c919 100644 --- a/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitizeToString.tentative.html +++ b/tests/wpt/web-platform-tests/sanitizer-api/sanitizer-sanitizeToString.https.tentative.html diff --git a/tests/wpt/web-platform-tests/scroll-animations/constructor.html b/tests/wpt/web-platform-tests/scroll-animations/constructor.html index eae6278f71b..1f5a8b5740e 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/constructor.html +++ b/tests/wpt/web-platform-tests/scroll-animations/constructor.html @@ -103,21 +103,49 @@ test(t => { assert_throws_js(TypeError, constructorFunc); }, 'Creating a ScrollTimeline with an invalid orientation value should throw'); -// startScrollOffset and endScrollOffset +// scrollOffsets + +function formatOffset(v) { + if (typeof(v) == 'object') + return `${v.constructor.name}(${v.toString()})`; + return `'${v.toString()}'`; +} + +function assert_offsets_equal(a, b) { + assert_equals(formatOffset(a), formatOffset(b)); +} + +test(t => { + assert_array_equals(new ScrollTimeline({timeRange: 100}).scrollOffsets, []); +}, 'A ScrollTimeline created with the default scrollOffsets should default to []'); test(t => { - assert_offsets_equal(new ScrollTimeline({timeRange: 100}).startScrollOffset, new CSSKeywordValue('auto')); -}, 'A ScrollTimeline created with the default startScrollOffset should default to CSSKeywordValue(auto)'); + assert_array_equals(new ScrollTimeline({timeRange: 100, scrollOffsets: []}).scrollOffsets, []); +}, 'A ScrollTimeline created with empty scrollOffsets should resolve to []'); test(t => { - assert_offsets_equal(new ScrollTimeline({timeRange: 100}).endScrollOffset, new CSSKeywordValue('auto')); -}, 'A ScrollTimeline created with the default endScrollOffset should default to CSSKeywordValue(auto)'); + let offsets = new ScrollTimeline({timeRange: 100, scrollOffsets: [CSS.percent(20), 'auto']}).scrollOffsets; + assert_offsets_equal(offsets[0], CSS.percent(20)); + assert_offsets_equal(offsets[1], new CSSKeywordValue('auto')); +}, 'A ScrollTimeline created with last \'auto\' offset in scrollOffsets should be allowed.'); + +test(t => { + let constructorFunc = function() { + new ScrollTimeline({timeRange: 100, scrollOffsets: null}) + }; + assert_throws_js(TypeError, constructorFunc); +}, 'Creating a ScrollTimeline with an invalid scrollOffsets value should throw'); + +test(t => { + let constructorFunc = function() { + new ScrollTimeline({timeRange: 100, scrollOffsets: ['auto']}) + }; + assert_throws_js(TypeError, constructorFunc); +}, 'Creating a ScrollTimeline with an scrollOffsets value of [\'auto\'] should throw'); const gValidScrollOffsetValues = [ - new CSSKeywordValue('auto'), CSS.px(0), CSS.percent(100).sub(CSS.px(80)), - "auto", ]; const gValidScrollOffsetSuffixes = [ @@ -145,26 +173,26 @@ const gValidScrollOffsetSuffixes = [ for (let offset of gValidScrollOffsetValues) { test(function() { const scrollTimeline = new ScrollTimeline( - {timeRange: 100, startScrollOffset: offset, endScrollOffset: offset}); + {timeRange: 100, scrollOffsets: [offset, offset]}); // Special case for 'auto'. This is valid input because of CSSKeywordish, // but when read back we expect a real CSSKeywordValue. if (offset === 'auto') offset = new CSSKeywordValue('auto'); - assert_offsets_equal(scrollTimeline.startScrollOffset, offset); - assert_offsets_equal(scrollTimeline.endScrollOffset, offset); - }, formatOffset(offset) + ' is a valid scroll offset value'); + assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset); + assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset); + }, '\'' + offset + '\' is a valid scroll offset value'); } for (const suffix of gValidScrollOffsetSuffixes) { test(function() { const offset = new CSSUnitValue(75, suffix); const scrollTimeline = new ScrollTimeline( - {timeRange: 100, startScrollOffset: offset, endScrollOffset: offset}); + {timeRange: 100, scrollOffsets: [offset, offset]}); - assert_offsets_equal(scrollTimeline.startScrollOffset, offset); - assert_offsets_equal(scrollTimeline.endScrollOffset, offset); + assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset); + assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset); }, '\'' + suffix + '\' is a valid scroll offset unit'); } @@ -199,10 +227,10 @@ for (const offset of gInvalidScrollOffsetValues) { test(function() { const constructorFunc = function() { new ScrollTimeline( - {timeRange: 100, startScrollOffset: offset, endScrollOffset: offset}) + {timeRange: 100, scrollOffsets: ['0px', offset]}) }; assert_throws_js(TypeError, constructorFunc); - }, formatOffset(offset) + ' is an invalid scroll offset value'); + }, formatOffset(offset) + ' is an invalid scroll offset value in scrollOffsets'); } for (const suffix of gInvalidScrollOffsetSuffixes) { @@ -210,13 +238,12 @@ for (const suffix of gInvalidScrollOffsetSuffixes) { const offset = '75' + suffix; const constructorFunc = function() { new ScrollTimeline( - {timeRange: 100, startScrollOffset: offset, endScrollOffset: offset}); + {timeRange: 100, scrollOffsets: ['0px', offset]}); }; assert_throws_js(TypeError, constructorFunc); - }, '\'' + suffix + '\' is an invalid scroll offset unit'); + }, '\'' + suffix + '\' is an invalid scroll offset unit in scrollOffsets'); } - const offset_target = document.createElement('div'); const gValidElementBasedScrollOffsetValues = [ @@ -229,20 +256,19 @@ const gValidElementBasedScrollOffsetValues = [ for (let offset of gValidElementBasedScrollOffsetValues) { test(function() { const scrollTimeline = new ScrollTimeline( - {timeRange: 100, startScrollOffset: offset, endScrollOffset: offset}); + {timeRange: 100, scrollOffsets: [offset, offset]}); // Special case unspecified threshold since it gets initialized to 0. if (!offset.hasOwnProperty('threshold')) offset.threshold = 0; - assert_equals(scrollTimeline.startScrollOffset.target, offset.target); - assert_equals(scrollTimeline.startScrollOffset.threshold, offset.threshold); - assert_equals(scrollTimeline.endScrollOffset.target, offset.target); - assert_equals(scrollTimeline.endScrollOffset.threshold, offset.threshold); + assert_equals(scrollTimeline.scrollOffsets[0].target, offset.target); + assert_equals(scrollTimeline.scrollOffsets[0].threshold, offset.threshold); + assert_equals(scrollTimeline.scrollOffsets[1].target, offset.target); + assert_equals(scrollTimeline.scrollOffsets[1].threshold, offset.threshold); }, '\'' + JSON.stringify(offset) + '\' is a valid scroll offset value'); } - const gInvalidElementBasedScrollOffsetValues = [ {}, // empty {target: offset_target, threshold: "test"}, @@ -254,10 +280,10 @@ for (let offset of gInvalidElementBasedScrollOffsetValues) { test(function() { const constructorFunc = function() { new ScrollTimeline( - {timeRange: 100, startScrollOffset: offset, endScrollOffset: offset}) + {timeRange: 100, scrollOffsets: [offset]}) }; assert_throws_js(TypeError, constructorFunc); - }, '\'' + JSON.stringify(offset) + '\' is an invalid scroll offset value'); + }, '\'' + JSON.stringify(offset) + '\' is an invalid scroll offset value in scrollOffsets'); } diff --git a/tests/wpt/web-platform-tests/scroll-animations/constructor.tentative.html b/tests/wpt/web-platform-tests/scroll-animations/constructor.tentative.html deleted file mode 100644 index 4510916a132..00000000000 --- a/tests/wpt/web-platform-tests/scroll-animations/constructor.tentative.html +++ /dev/null @@ -1,208 +0,0 @@ -<!DOCTYPE html> -<meta charset="utf-8"> -<title>ScrollTimeline constructor</title> - <link rel="help" href="https://wicg.github.io/scroll-animations/#scrolltimeline-interface"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> - -<style> -.scroller { - height: 100px; - width: 100px; - overflow: scroll; -} - -.content { - height: 500px; - width: 500px; -} -</style> - -<div class="scroller"> - <div class="content"></div> -</div> - -<script> -'use strict'; - -function formatOffset(v) { - if (typeof(v) == 'object') - return `${v.constructor.name}(${v.toString()})`; - return `'${v.toString()}'`; -} - -function assert_offsets_equal(a, b) { - assert_equals(formatOffset(a), formatOffset(b)); -} - -test(t => { - assert_array_equals(new ScrollTimeline({timeRange: 100}).scrollOffsets, []); -}, 'A ScrollTimeline created with the default scrollOffsets should default to []'); - -test(t => { - assert_array_equals(new ScrollTimeline({timeRange: 100, scrollOffsets: []}).scrollOffsets, []); -}, 'A ScrollTimeline created with empty scrollOffsets should resolve to []'); - -test(t => { - let offsets = new ScrollTimeline({timeRange: 100, scrollOffsets: [CSS.percent(20), 'auto']}).scrollOffsets; - assert_offsets_equal(offsets[0], CSS.percent(20)); - assert_offsets_equal(offsets[1], new CSSKeywordValue('auto')); -}, 'A ScrollTimeline created with last \'auto\' offset in scrollOffsets should be allowed.'); - -test(t => { - let constructorFunc = function() { - new ScrollTimeline({timeRange: 100, scrollOffsets: null}) - }; - assert_throws_js(TypeError, constructorFunc); -}, 'Creating a ScrollTimeline with an invalid scrollOffsets value should throw'); - -test(t => { - let constructorFunc = function() { - new ScrollTimeline({timeRange: 100, scrollOffsets: ['auto']}) - }; - assert_throws_js(TypeError, constructorFunc); -}, 'Creating a ScrollTimeline with an scrollOffsets value of [\'auto\'] should throw'); - -const gValidScrollOffsetValues = [ - CSS.px(0), - CSS.percent(100).sub(CSS.px(80)), -]; - -const gValidScrollOffsetSuffixes = [ - // Relative lengths. - 'em', - 'ex', - 'ch', - 'rem', - 'vw', - 'vh', - 'vmin', - 'vmax', - // Absolute lengths. - 'cm', - 'mm', - 'q', - 'in', - 'pc', - 'pt', - 'px', - // Percentage. - '%', -]; - -for (let offset of gValidScrollOffsetValues) { - test(function() { - const scrollTimeline = new ScrollTimeline( - {timeRange: 100, scrollOffsets: [offset, offset]}); - - // Special case for 'auto'. This is valid input because of CSSKeywordish, - // but when read back we expect a real CSSKeywordValue. - if (offset === 'auto') - offset = new CSSKeywordValue('auto'); - - assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset); - assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset); - }, '\'' + offset + '\' is a valid scroll offset value'); -} - -for (const suffix of gValidScrollOffsetSuffixes) { - test(function() { - const offset = new CSSUnitValue(75, suffix); - const scrollTimeline = new ScrollTimeline( - {timeRange: 100, scrollOffsets: [offset, offset]}); - - assert_offsets_equal(scrollTimeline.scrollOffsets[0], offset); - assert_offsets_equal(scrollTimeline.scrollOffsets[1], offset); - }, '\'' + suffix + '\' is a valid scroll offset unit'); -} - -// These are deliberately incomplete, just a random sampling of invalid -// values/units. -const gInvalidScrollOffsetValues = [ - '', - 'calc(360deg / 4)', - 'left', - '#ff0000', - 'rgb(0, 128, 0)', - 'url("http://www.example.com/pinkish.gif")', - 'this_is_garbage', - CSS.number(0), - // Multiple valid values. - '100px 5%', - // Values that would be valid if represented with CSS Typed OM: - 0, - '10px', - '10%', - 'calc(100% - 80px)', -]; - -const gInvalidScrollOffsetSuffixes = [ - 'deg', - 's', - 'Hz', - 'dpi', -]; - -for (const offset of gInvalidScrollOffsetValues) { - test(function() { - const constructorFunc = function() { - new ScrollTimeline( - {timeRange: 100, scrollOffsets: ['0px', offset]}) - }; - assert_throws_js(TypeError, constructorFunc); - }, formatOffset(offset) + ' is an invalid scroll offset value in scrollOffsets'); -} - -for (const suffix of gInvalidScrollOffsetSuffixes) { - test(function() { - const offset = '75' + suffix; - const constructorFunc = function() { - new ScrollTimeline( - {timeRange: 100, scrollOffsets: ['0px', offset]}); - }; - assert_throws_js(TypeError, constructorFunc); - }, '\'' + suffix + '\' is an invalid scroll offset unit in scrollOffsets'); -} - -const offset_target = document.createElement('div'); - -const gValidElementBasedScrollOffsetValues = [ - {target: offset_target}, - {target: offset_target, threshold: 0}, - {target: offset_target, threshold: 0.5}, - {target: offset_target, threshold: 1}, -]; - -for (let offset of gValidElementBasedScrollOffsetValues) { - test(function() { - const scrollTimeline = new ScrollTimeline( - {timeRange: 100, scrollOffsets: [offset, offset]}); - - // Special case unspecified threshold since it gets initialized to 0. - if (!offset.hasOwnProperty('threshold')) - offset.threshold = 0; - - assert_equals(scrollTimeline.scrollOffsets[0].target, offset.target); - assert_equals(scrollTimeline.scrollOffsets[0].threshold, offset.threshold); - assert_equals(scrollTimeline.scrollOffsets[1].target, offset.target); - assert_equals(scrollTimeline.scrollOffsets[1].threshold, offset.threshold); - }, '\'' + JSON.stringify(offset) + '\' is a valid scroll offset value'); -} - -const gInvalidElementBasedScrollOffsetValues = [ - {}, // empty - {target: offset_target, threshold: "test"}, - {target: offset_target, threshold: 2}, - {target: offset_target, threshold: -0.2}, -]; - -for (let offset of gInvalidElementBasedScrollOffsetValues) { - test(function() { - const constructorFunc = function() { - new ScrollTimeline( - {timeRange: 100, scrollOffsets: [offset]}) - }; - assert_throws_js(TypeError, constructorFunc); - }, '\'' + JSON.stringify(offset) + '\' is an invalid scroll offset value in scrollOffsets'); -} -</script> diff --git a/tests/wpt/web-platform-tests/scroll-animations/current-time-writing-modes.html b/tests/wpt/web-platform-tests/scroll-animations/current-time-writing-modes.html index c5c1fe155a8..b5b7eabca78 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/current-time-writing-modes.html +++ b/tests/wpt/web-platform-tests/scroll-animations/current-time-writing-modes.html @@ -190,19 +190,19 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal', - startScrollOffset: CSS.px(20) + scrollOffsets: [CSS.px(20), 'auto'] }); const percentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal', - startScrollOffset: CSS.percent(20) + scrollOffsets: [CSS.percent(20), 'auto'] }); const calcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal', - startScrollOffset: CSS.percent(20).sub(CSS.px(5)) + scrollOffsets: [CSS.percent(20).sub(CSS.px(5)), 'auto'] }); // Unscrolled, all timelines should read a current time of 0, since @@ -293,19 +293,19 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal', - endScrollOffset: CSS.px(scrollerSize - 20) + scrollOffsets: [CSS.px(scrollerSize - 20)] }); const percentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal', - endScrollOffset: CSS.percent(80) + scrollOffsets: [CSS.percent(80)] }); const calcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'horizontal', - endScrollOffset: CSS.percent(80).add(CSS.px(5)) + scrollOffsets: [CSS.percent(80).add(CSS.px(5))] }); // With direction rtl offsets are inverted, such that scrollLeft == 0 @@ -387,25 +387,24 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: 'auto' }); const inclusiveLengthScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.px(scrollerSize) + scrollOffsets: [CSS.px(scrollerSize)] }); const inclusivePercentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.percent(100) + scrollOffsets: [CSS.percent(100)] }); const inclusiveCalcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.percent(80).sub(CSS.px(0.2 * scrollerSize)) + scrollOffsets: [CSS.percent(80).sub(CSS.px(0.2 * scrollerSize))] }); // With direction rtl offsets are inverted, such that scrollLeft == diff --git a/tests/wpt/web-platform-tests/scroll-animations/current-time.html b/tests/wpt/web-platform-tests/scroll-animations/current-time.html index fe8c64a6add..6682939b1c4 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/current-time.html +++ b/tests/wpt/web-platform-tests/scroll-animations/current-time.html @@ -97,19 +97,19 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - startScrollOffset: CSS.px(20) + scrollOffsets: [CSS.px(20), 'auto'] }); const percentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - startScrollOffset: CSS.percent(20) + scrollOffsets: [CSS.percent(20), 'auto'] }); const calcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - startScrollOffset: CSS.percent(20).sub(CSS.px(5)) + scrollOffsets: [CSS.percent(20).sub(CSS.px(5)), 'auto'] }); // Unscrolled all timelines should read a current time of 0, as the @@ -236,25 +236,24 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: 'auto' }); const inclusiveLengthScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.px(scrollerSize) + scrollOffsets: [CSS.px(scrollerSize)] }); const inclusivePercentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.percent(100) + scrollOffsets: [CSS.percent(100)] }); const inclusiveCalcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.percent(80).add(CSS.px(0.2 * scrollerSize)) + scrollOffsets: [CSS.percent(80).add(CSS.px(0.2 * scrollerSize))] }); scroller.scrollTop = scrollerSize; @@ -290,19 +289,19 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.px(scrollerSize - 20) + scrollOffsets: [CSS.px(scrollerSize - 20)] }); const percentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.percent(80) + scrollOffsets: [CSS.percent(80)] }); const calcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - endScrollOffset: CSS.percent(80).add(CSS.px(5)) + scrollOffsets: [CSS.percent(80).add(CSS.px(5))] }); // Check the length-based ScrollTimeline. @@ -403,8 +402,7 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - startScrollOffset: CSS.px(20), - endScrollOffset: CSS.px(scrollerSize - 50) + scrollOffsets: [CSS.px(20), CSS.px(scrollerSize - 50)] }); scroller.scrollTop = 150; @@ -427,8 +425,7 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - startScrollOffset: CSS.px(20), - endScrollOffset: CSS.px(20) + scrollOffsets: [CSS.px(20), CSS.px(20)] }); scroller.scrollTop = 150; @@ -449,8 +446,7 @@ promise_test(async t => { scrollSource: scroller, timeRange: scrollerSize, orientation: 'block', - startScrollOffset: CSS.px(50), - endScrollOffset: CSS.px(10) + scrollOffsets: [CSS.px(50), CSS.px(10)] }); scroller.scrollTop = 40; diff --git a/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-clamp.html b/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-clamp.html index d6da677a055..57601a8792d 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-clamp.html +++ b/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-clamp.html @@ -85,8 +85,8 @@ orientation: config.orientation, timeRange: 1000, fill: 'both', - startScrollOffset: {target: target, edge: 'end', ...config.start}, - endScrollOffset: {target: target, edge:'start', ...config.end } + scrollOffsets: [{target: target, edge: 'end', ...config.start}, + {target: target, edge:'start', ...config.end }] }); // Wait for new animation frame which allows the timeline to compute new diff --git a/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-unresolved.html b/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-unresolved.html index 65173d4a18e..7b128175c26 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-unresolved.html +++ b/tests/wpt/web-platform-tests/scroll-animations/element-based-offset-unresolved.html @@ -57,8 +57,7 @@ promise_test(async t => { scrollSource: scroller, orientation: 'block', timeRange: 1000, - startScrollOffset: { target: not_a_descendant }, - endScrollOffset: { target: end } + scrollOffsets: [{ target: not_a_descendant }, { target: end }] }); await waitForNextFrame(); @@ -77,8 +76,7 @@ promise_test(async t => { scrollSource: scroller, orientation: 'block', timeRange: 1000, - startScrollOffset: { target: start }, - endScrollOffset: { target: end } + scrollOffsets: [{ target: start }, { target: end }] }); start.style.display = "none"; diff --git a/tests/wpt/web-platform-tests/scroll-animations/element-based-offset.html b/tests/wpt/web-platform-tests/scroll-animations/element-based-offset.html index d485e9ef9c4..1abb3481938 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/element-based-offset.html +++ b/tests/wpt/web-platform-tests/scroll-animations/element-based-offset.html @@ -77,8 +77,8 @@ orientation: config.orientation, timeRange: 1000, fill: 'both', - startScrollOffset: {target: start, ...config.start}, - endScrollOffset: {target: end, ...config.end } + scrollOffsets: + [{target: start, ...config.start}, {target: end, ...config.end }] }); // Wait for new animation frame which allows the timeline to compute new diff --git a/tests/wpt/web-platform-tests/scroll-animations/layout-changes-on-percentage-based-timeline.html b/tests/wpt/web-platform-tests/scroll-animations/layout-changes-on-percentage-based-timeline.html index 19fce372133..1fe17b44d26 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/layout-changes-on-percentage-based-timeline.html +++ b/tests/wpt/web-platform-tests/scroll-animations/layout-changes-on-percentage-based-timeline.html @@ -64,8 +64,7 @@ layout changes on percentage-based scroll offset"> const timeline = new ScrollTimeline({ scrollSource: scroller, timeRange: 1000, - startScrollOffset: CSS.percent(20), - endScrollOffset: CSS.percent(80) + scrollOffsets: [CSS.percent(20), CSS.percent(80)] }); const animation = new Animation(effect, timeline); animation.play(); diff --git a/tests/wpt/web-platform-tests/scroll-animations/multiple-scroll-offsets.tentative.html b/tests/wpt/web-platform-tests/scroll-animations/multiple-scroll-offsets.html index 026b2e7673c..026b2e7673c 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/multiple-scroll-offsets.tentative.html +++ b/tests/wpt/web-platform-tests/scroll-animations/multiple-scroll-offsets.html diff --git a/tests/wpt/web-platform-tests/scroll-animations/progress-based-current-time.tenative.html b/tests/wpt/web-platform-tests/scroll-animations/progress-based-current-time.tenative.html index 8a8ccfcff8b..34702b865ad 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/progress-based-current-time.tenative.html +++ b/tests/wpt/web-platform-tests/scroll-animations/progress-based-current-time.tenative.html @@ -82,17 +82,17 @@ promise_test(async t => { const lengthScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - startScrollOffset: CSS.px(20) + scrollOffsets: [CSS.px(20), 'auto'] }); const percentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - startScrollOffset: CSS.percent(20) + scrollOffsets: [CSS.percent(20), 'auto'] }); const calcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - startScrollOffset: CSS.percent(20).sub(CSS.px(5)) + scrollOffsets: [CSS.percent(20).sub(CSS.px(5)), 'auto'] }); // Unscrolled all timelines should read a current time of 0, as the @@ -215,22 +215,21 @@ promise_test(async t => { const inclusiveAutoScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - endScrollOffset: 'auto' }); const inclusiveLengthScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - endScrollOffset: CSS.px(scrollerSize) + scrollOffsets: [CSS.px(scrollerSize)] }); const inclusivePercentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - endScrollOffset: CSS.percent(100) + scrollOffsets: [CSS.percent(100)] }); const inclusiveCalcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - endScrollOffset: CSS.percent(80).add(CSS.px(0.2 * scrollerSize)) + scrollOffsets: [CSS.percent(80).add(CSS.px(0.2 * scrollerSize))] }); scroller.scrollTop = scrollerSize; @@ -262,17 +261,17 @@ promise_test(async t => { const lengthScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - endScrollOffset: CSS.px(scrollerSize - 20) + scrollOffsets: [CSS.px(scrollerSize - 20)] }); const percentageScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - endScrollOffset: CSS.percent(80) + scrollOffsets: [CSS.percent(80)] }); const calcScrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - endScrollOffset: CSS.percent(80).add(CSS.px(5)) + scrollOffsets: [CSS.percent(80).add(CSS.px(5))] }); // Check the length-based ScrollTimeline. @@ -369,8 +368,7 @@ promise_test(async t => { const scrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - startScrollOffset: CSS.px(20), - endScrollOffset: CSS.px(scrollerSize - 50) + scrollOffsets: [CSS.px(20), CSS.px(scrollerSize - 50)] }); scroller.scrollTop = 150; @@ -387,8 +385,7 @@ promise_test(async t => { const scrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - startScrollOffset: CSS.px(20), - endScrollOffset: CSS.px(20) + scrollOffsets: [CSS.px(20), CSS.px(20)] }); scroller.scrollTop = 150; @@ -404,8 +401,7 @@ promise_test(async t => { const scrollTimeline = new ScrollTimeline({ scrollSource: scroller, orientation: 'block', - startScrollOffset: CSS.px(50), - endScrollOffset: CSS.px(10) + scrollOffsets: [CSS.px(50), CSS.px(10)] }); scroller.scrollTop = 40; diff --git a/tests/wpt/web-platform-tests/scroll-animations/testcommon.js b/tests/wpt/web-platform-tests/scroll-animations/testcommon.js index 9b3e82228fc..811e802ba56 100644 --- a/tests/wpt/web-platform-tests/scroll-animations/testcommon.js +++ b/tests/wpt/web-platform-tests/scroll-animations/testcommon.js @@ -32,8 +32,7 @@ function createScrollTimelineWithOffsets(test, startOffset, endOffset) { return createScrollTimeline(test, { scrollSource: createScroller(test), orientation: "vertical", - startScrollOffset: startOffset, - endScrollOffset: endOffset, + scrollOffsets: [startOffset, endOffset], timeRange: 1000 }); } diff --git a/tests/wpt/web-platform-tests/selection/modify.tentative.html b/tests/wpt/web-platform-tests/selection/modify.tentative.html index c4436fcafeb..37231571edd 100644 --- a/tests/wpt/web-platform-tests/selection/modify.tentative.html +++ b/tests/wpt/web-platform-tests/selection/modify.tentative.html @@ -6,6 +6,21 @@ <div>Test, these are <strong id="strong"> strong </strong> <em id="em"> italic </em> normal.</div> +<pre id="preLinefeed"> +foo +bar +</pre> + +<pre id="preBr"> +foo<br>bar +</pre> + +<pre id="preLinefeedBr"> +foo +<br> +bar +</pre> + <script> const selection = getSelection(); test(() => { @@ -18,4 +33,58 @@ test(() => { assert_equals(selection.focusNode, em.childNodes[0]); assert_equals(selection.focusOffset, 1); }, "Stop at previous word boundary when whitespaces are trimmed"); + +test(() => { + const preLinefeed = document.getElementById("preLinefeed"); + const textChild = preLinefeed.childNodes[0]; + selection.collapse(textChild, 3); + selection.modify("move", "forward", "character"); + assert_equals(selection.focusNode, textChild); + assert_equals(selection.focusOffset, 4); +}, "Jump linefeed forward"); + +test(() => { + const preLinefeed = document.getElementById("preLinefeed"); + const textChild = preLinefeed.childNodes[0]; + selection.collapse(textChild, 4); + selection.modify("move", "backward", "character"); + assert_equals(selection.focusNode, textChild); + assert_equals(selection.focusOffset, 3); +}, "Jump linefeed backward"); + +test(() => { + const preBr = document.getElementById("preBr"); + const [firstTextChild, br, secondTextChild] = preBr.childNodes; + selection.collapse(firstTextChild, 3); + selection.modify("move", "forward", "character"); + assert_equals(selection.focusNode, secondTextChild); + assert_equals(selection.focusOffset, 0); +}, "Jump <br> forward"); + +test(() => { + const preBr = document.getElementById("preBr"); + const [firstTextChild, br, secondTextChild] = preBr.childNodes; + selection.collapse(secondTextChild, 0); + selection.modify("move", "backward", "character"); + assert_equals(selection.focusNode, firstTextChild); + assert_equals(selection.focusOffset, 3); +}, "Jump <br> backward"); + +test(() => { + const preLinefeedBr = document.getElementById("preLinefeedBr"); + selection.collapse(preLinefeedBr, 1); + selection.modify("move", "forward", "character"); + const secondTextChild = preLinefeedBr.childNodes[2]; + assert_equals(selection.focusNode, secondTextChild); + assert_equals(selection.focusOffset, 0); +}, "Jump <br> forward which follows a linefeed"); + +test(() => { + const preLinefeedBr = document.getElementById("preLinefeedBr"); + selection.collapse(preLinefeedBr, 2); + selection.modify("move", "backward", "character"); + const textChild = preLinefeedBr.childNodes[0]; + assert_equals(selection.focusNode, textChild); + assert_equals(selection.focusOffset, textChild.textContent.length); +}, "Jump <br> backward which follows a linefeed"); </script> diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event.https.html b/tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event.https.html index 48f71e46cb2..4c79670de37 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event.https.html +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/fetch-event.https.html @@ -480,6 +480,137 @@ promise_test(t => { }); }, 'FetchEvent#body is a string and is passed to network fallback'); +// Test that the request body is sent to network upon network fallback, +// for a ReadableStream body. +promise_test(async t => { + const rs = new ReadableStream({start(c) { + c.enqueue('i a'); + c.enqueue('m the request'); + t.step_timeout(t.step_func(() => { + c.enqueue(' body'); + c.close(); + }, 10)); + }}); + // Set page_url to "?ignore" so the service worker falls back to network + // for the main resource request, and add a suffix to avoid colliding + // with other tests. + const page_url = 'resources/?ignore-for-request-body-fallback-string'; + const frame = await with_iframe(page_url); + t.add_cleanup(() => { frame.remove(); }); + // Add "?ignore" so the service worker falls back to echo-content.py. + const echo_url = '/fetch/api/resources/echo-content.py?ignore'; + const response = await frame.contentWindow.fetch(echo_url, { + method: 'POST', + body: rs + }); + const text = await response.text(); + assert_equals(text, + 'i am the request body', + 'the network fallback request should include the request body'); + }, 'FetchEvent#body is a ReadableStream and is passed to network fallback'); + +// Test that the request body is sent to network upon network fallback even when +// the request body is used in the service worker, for a string body. +promise_test(async t => { + // Set page_url to "?ignore" so the service worker falls back to network + // for the main resource request, and add a suffix to avoid colliding + // with other tests. + const page_url = 'resources/?ignore-for-request-body-fallback-string'; + + const frame = await with_iframe(page_url); + t.add_cleanup(() => { frame.remove(); }); + // Add "?use-and-ignore" so the service worker falls back to echo-content.py. + const echo_url = '/fetch/api/resources/echo-content.py?use-and-ignore'; + const response = await frame.contentWindow.fetch(echo_url, { + method: 'POST', + body: 'i am the request body' + }); + const text = await response.text(); + assert_equals( + text, + 'i am the request body', + 'the network fallback request should include the request body'); + }, 'FetchEvent#body is a string, used and passed to network fallback'); + +// When the streaming body is used in the service worker, network fallback +// fails. +promise_test(async t => { + const rs = new ReadableStream({start(c) { + c.enqueue('i a'); + c.enqueue('m the request'); + t.step_timeout(t.step_func(() => { + c.enqueue(' body'); + c.close(); + }, 10)); + }}); + // Set page_url to "?ignore" so the service worker falls back to network + // for the main resource request, and add a suffix to avoid colliding + // with other tests. + const page_url = 'resources/?ignore-for-request-body-fallback-string'; + const frame = await with_iframe(page_url); + t.add_cleanup(() => { frame.remove(); }); + const echo_url = '/fetch/api/resources/echo-content.py?use-and-ignore'; + await promise_rejects_js(t, TypeError, frame.contentWindow.fetch(echo_url, { + method: 'POST', + body: rs + })); + }, 'FetchEvent#body is a ReadableStream, used and passed to network fallback'); + +// Test that the request body is sent to network upon network fallback even when +// the request body is used by clone() in the service worker, for a string body. +promise_test(async t => { + // Set page_url to "?ignore" so the service worker falls back to network + // for the main resource request, and add a suffix to avoid colliding + // with other tests. + const page_url = 'resources/?ignore-for-request-body-fallback-string'; + + const frame = await with_iframe(page_url); + t.add_cleanup(() => { frame.remove(); }); + // Add "?clone-and-ignore" so the service worker falls back to + // echo-content.py. + const echo_url = '/fetch/api/resources/echo-content.py?clone-and-ignore'; + const response = await frame.contentWindow.fetch(echo_url, { + method: 'POST', + body: 'i am the request body' + }); + const text = await response.text(); + assert_equals( + text, + 'i am the request body', + 'the network fallback request should include the request body'); + }, 'FetchEvent#body is a string, cloned and passed to network fallback'); + +// When the streaming body is used by clone() in the service worker, network +// fallback fails. +promise_test(async t => { + const rs = new ReadableStream({start(c) { + c.enqueue('i a'); + c.enqueue('m the request'); + t.step_timeout(t.step_func(() => { + c.enqueue(' body'); + c.close(); + }, 10)); + }}); + // Set page_url to "?ignore" so the service worker falls back to network + // for the main resource request, and add a suffix to avoid colliding + // with other tests. + const page_url = 'resources/?ignore-for-request-body-fallback-string'; + const frame = await with_iframe(page_url); + t.add_cleanup(() => { frame.remove(); }); + // Add "?clone-and-ignore" so the service worker falls back to + // echo-content.py. + const echo_url = '/fetch/api/resources/echo-content.py?clone-and-ignore'; + const response = await frame.contentWindow.fetch(echo_url, { + method: 'POST', + body: 'i am the request body' + }); + const text = await response.text(); + assert_equals( + text, + 'i am the request body', + 'the network fallback request should include the request body'); + }, 'FetchEvent#body is a ReadableStream, cloned and passed to network fallback'); + // Test that the service worker can read FetchEvent#body when it is a blob. // It responds with request body it read. promise_test(t => { @@ -851,5 +982,52 @@ promise_test(async (t) => { assert_equals(frame.contentDocument.body.textContent, 'method = POST, isHistoryNavigation = true'); }, 'FetchEvent#request.isHistoryNavigation is true (POST + history.go(-1))'); + +// When service worker responds with a Response, no XHR upload progress +// events are delivered. +promise_test(async t => { + const page_url = 'resources/simple.html?ignore-for-request-body-string'; + const frame = await with_iframe(page_url); + t.add_cleanup(() => { frame.remove(); }); + + const xhr = new frame.contentWindow.XMLHttpRequest(); + xhr.open('POST', 'simple.html?request-body'); + xhr.upload.addEventListener('progress', t.unreached_func('progress')); + xhr.upload.addEventListener('error', t.unreached_func('error')); + xhr.upload.addEventListener('abort', t.unreached_func('abort')); + xhr.upload.addEventListener('timeout', t.unreached_func('timeout')); + xhr.upload.addEventListener('load', t.unreached_func('load')); + xhr.upload.addEventListener('loadend', t.unreached_func('loadend')); + xhr.send('i am the request body'); + + await new Promise((resolve) => xhr.addEventListener('load', resolve)); + }, 'XHR upload progress events for response coming from SW'); + +// Upload progress events should be delivered for the network fallback case. +promise_test(async t => { + const page_url = 'resources/simple.html?ignore-for-request-body-string'; + const frame = await with_iframe(page_url); + t.add_cleanup(() => { frame.remove(); }); + + let progress = false; + let load = false; + let loadend = false; + + const xhr = new frame.contentWindow.XMLHttpRequest(); + xhr.open('POST', '/fetch/api/resources/echo-content.py?ignore'); + xhr.upload.addEventListener('progress', () => progress = true); + xhr.upload.addEventListener('error', t.unreached_func('error')); + xhr.upload.addEventListener('abort', t.unreached_func('abort')); + xhr.upload.addEventListener('timeout', t.unreached_func('timeout')); + xhr.upload.addEventListener('load', () => load = true); + xhr.upload.addEventListener('loadend', () => loadend = true); + xhr.send('i am the request body'); + + await new Promise((resolve) => xhr.addEventListener('load', resolve)); + assert_true(progress, 'progress'); + assert_true(load, 'load'); + assert_true(loadend, 'loadend'); + }, 'XHR upload progress events for network fallback'); + </script> </body> diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/navigation-redirect-resolution.https.html b/tests/wpt/web-platform-tests/service-workers/service-worker/navigation-redirect-resolution.https.html new file mode 100644 index 00000000000..59e1cafec34 --- /dev/null +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/navigation-redirect-resolution.https.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<title>Service Worker: Navigation Redirect Resolution</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> + +function make_absolute(url) { + return new URL(url, location).toString(); +} + +const script = 'resources/fetch-rewrite-worker.js'; + +function redirect_result_test(scope, expected_url, description) { + promise_test(async t => { + const registration = await service_worker_unregister_and_register( + t, script, scope); + t.add_cleanup(() => { + return service_worker_unregister(t, scope); + }) + await wait_for_state(t, registration.installing, 'activated'); + + // The navigation to |scope| will be resolved by a fetch to |redirect_url| + // which returns a relative Location header. If it is resolved relative to + // |scope|, the result will be navigate-redirect-resolution/blank.html. If + // relative to |redirect_url|, it will be resources/blank.html. The latter + // is correct. + const iframe = await with_iframe(scope); + t.add_cleanup(() => { iframe.remove(); }); + assert_equals(iframe.contentWindow.location.href, + make_absolute(expected_url)); + }, description); +} + +// |redirect_url| serves a relative redirect to resources/blank.html. +const redirect_url = 'resources/redirect.py?Redirect=blank.html'; + +// |scope_base| does not exist but will be replaced with a fetch of +// |redirect_url| by fetch-rewrite-worker.js. +const scope_base = 'resources/subdir/navigation-redirect-resolution?' + + 'redirect-mode=manual&url=' + + encodeURIComponent(make_absolute(redirect_url)); + +// When the Service Worker forwards the result of |redirect_url| as an +// opaqueredirect response, the redirect uses the response's URL list as the +// base URL, not the request. +redirect_result_test(scope_base, 'resources/blank.html', + 'test relative opaqueredirect'); + +// The response's base URL should be preserved across CacheStorage and clone. +redirect_result_test(scope_base + '&cache=1', 'resources/blank.html', + 'test relative opaqueredirect with CacheStorage'); +redirect_result_test(scope_base + '&clone=1', 'resources/blank.html', + 'test relative opaqueredirect with clone'); + +</script> +</body> diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/redirected-response.https.html b/tests/wpt/web-platform-tests/service-workers/service-worker/redirected-response.https.html index d2c7858bedd..71b35d0e120 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/redirected-response.https.html +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/redirected-response.https.html @@ -297,7 +297,7 @@ promise_test(t => setup_and_clean() .then(() => { const url = host_info['HTTPS_ORIGIN'] + base_path() + 'sample?url=' + encodeURIComponent(TARGET_URL) + - '&original-redirect-mode=follow&sw=gen'; + '&original-redirect-mode=manual&sw=gen'; return redirected_test({url: url, fetch_option: {redirect: 'manual'}, fetch_method: frame.contentWindow.fetch, @@ -309,6 +309,102 @@ promise_test(t => setup_and_clean() // ======================================================= // Tests for requests that are in-scope of the service worker. The service +// worker returns a generated redirect response manually with the Response +// constructor. +// ======================================================= +promise_test(t => setup_and_clean() + .then(() => { + const url = host_info['HTTPS_ORIGIN'] + base_path() + + 'sample?url=' + encodeURIComponent(TARGET_URL) + + '&original-redirect-mode=follow&sw=gen-manual'; + return redirected_test({url: url, + fetch_option: {redirect: 'follow'}, + fetch_method: frame.contentWindow.fetch, + expected_type: 'basic', + expected_redirected: true, + expected_intercepted_urls: [url, TARGET_URL]}) + }), + 'mode: "follow", manually-generated redirect response'); + +promise_test(t => setup_and_clean() + .then(() => { + const url = host_info['HTTPS_ORIGIN'] + base_path() + + 'sample?url=' + encodeURIComponent(TARGET_URL) + + '&original-redirect-mode=error&sw=gen-manual'; + return promise_rejects_js( + t, frame.contentWindow.TypeError, + frame.contentWindow.fetch(url, {redirect: 'error'}), + 'The generated redirect response from the service worker should ' + + 'be treated as an error when the redirect flag of request was' + + ' \'error\'.') + .then(() => check_intercepted_urls([url])); + }), + 'mode: "error", manually-generated redirect response'); + +promise_test(t => setup_and_clean() + .then(() => { + const url = host_info['HTTPS_ORIGIN'] + base_path() + + 'sample?url=' + encodeURIComponent(TARGET_URL) + + '&original-redirect-mode=manual&sw=gen-manual'; + return redirected_test({url: url, + fetch_option: {redirect: 'manual'}, + fetch_method: frame.contentWindow.fetch, + expected_type: 'opaqueredirect', + expected_redirected: false, + expected_intercepted_urls: [url]}) + }), + 'mode: "manual", manually-generated redirect response'); + +// ======================================================= +// Tests for requests that are in-scope of the service worker. The service +// worker returns a generated redirect response with a relative location header. +// Generated responses do not have URLs, so this should fail to resolve. +// ======================================================= +promise_test(t => setup_and_clean() + .then(() => { + const url = host_info['HTTPS_ORIGIN'] + base_path() + + 'sample?url=blank.html' + + '&original-redirect-mode=follow&sw=gen-manual'; + return promise_rejects_js( + t, frame.contentWindow.TypeError, + frame.contentWindow.fetch(url, {redirect: 'follow'}), + 'Following the generated redirect response from the service worker '+ + 'should result fail.') + .then(() => check_intercepted_urls([url])); + }), + 'mode: "follow", generated relative redirect response'); + +promise_test(t => setup_and_clean() + .then(() => { + const url = host_info['HTTPS_ORIGIN'] + base_path() + + 'sample?url=blank.html' + + '&original-redirect-mode=error&sw=gen-manual'; + return promise_rejects_js( + t, frame.contentWindow.TypeError, + frame.contentWindow.fetch(url, {redirect: 'error'}), + 'The generated redirect response from the service worker should ' + + 'be treated as an error when the redirect flag of request was' + + ' \'error\'.') + .then(() => check_intercepted_urls([url])); + }), + 'mode: "error", generated relative redirect response'); + +promise_test(t => setup_and_clean() + .then(() => { + const url = host_info['HTTPS_ORIGIN'] + base_path() + + 'sample?url=blank.html' + + '&original-redirect-mode=manual&sw=gen-manual'; + return redirected_test({url: url, + fetch_option: {redirect: 'manual'}, + fetch_method: frame.contentWindow.fetch, + expected_type: 'opaqueredirect', + expected_redirected: false, + expected_intercepted_urls: [url]}) + }), + 'mode: "manual", generated relative redirect response'); + +// ======================================================= +// Tests for requests that are in-scope of the service worker. The service // worker returns a generated redirect response. And the fetch follows the // redirection multiple times. // ======================================================= diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/registration-basic.https.html b/tests/wpt/web-platform-tests/service-workers/service-worker/registration-basic.https.html index 1411a5d48cd..759b4244a26 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/registration-basic.https.html +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/registration-basic.https.html @@ -3,7 +3,37 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="resources/test-helpers.sub.js"></script> -<script src="resources/registration-tests-basic.js"></script> <script> -registration_tests_basic((script, options) => navigator.serviceWorker.register(script, options), true); +const script = 'resources/registration-worker.js'; + +promise_test(async (t) => { + const scope = 'resources/registration/normal'; + const registration = await navigator.serviceWorker.register(script, {scope}); + t.add_cleanup(() => registration.unregister()); + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); +}, 'Registering normal scope'); + +promise_test(async (t) => { + const scope = 'resources/registration/scope-with-fragment#ref'; + const registration = await navigator.serviceWorker.register(script, {scope}); + t.add_cleanup(() => registration.unregister()); + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); + assert_equals( + registration.scope, + normalizeURL('resources/registration/scope-with-fragment'), + 'A fragment should be removed from scope'); +}, 'Registering scope with fragment'); + +promise_test(async (t) => { + const scope = 'resources/'; + const registration = await navigator.serviceWorker.register(script, {scope}) + t.add_cleanup(() => registration.unregister()); + assert_true( + registration instanceof ServiceWorkerRegistration, + 'Successfully registered.'); +}, 'Registering same scope as the script directory'); </script> diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-test-worker.js b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-test-worker.js index 0a52a8201ea..ba8204b9bb8 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-test-worker.js +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-event-test-worker.js @@ -155,6 +155,18 @@ function handleIsHistoryNavigation(event) { event.respondWith(new Response(body)); } +function handleUseAndIgnore(event) { + const request = event.request; + request.text(); + return; +} + +function handleCloneAndIgnore(event) { + const request = event.request; + request.clone().text(); + return; +} + self.addEventListener('fetch', function(event) { var url = event.request.url; var handlers = [ @@ -180,6 +192,8 @@ self.addEventListener('fetch', function(event) { { pattern: '?keepalive', fn: handleKeepalive }, { pattern: '?isReloadNavigation', fn: handleIsReloadNavigation }, { pattern: '?isHistoryNavigation', fn: handleIsHistoryNavigation }, + { pattern: '?use-and-ignore', fn: handleUseAndIgnore }, + { pattern: '?clone-and-ignore', fn: handleCloneAndIgnore }, ]; var handler = null; diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-rewrite-worker.js b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-rewrite-worker.js index f2d49e2706a..4631e83e0ce 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-rewrite-worker.js +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/fetch-rewrite-worker.js @@ -133,6 +133,10 @@ self.addEventListener('fetch', function(event) { } } + if (params['clone']) { + response = response.clone(); + } + // |cache| means to bounce responses through Cache Storage and back. if (params['cache']) { var cacheName = "cached-fetches-" + performance.now() + "-" + diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/redirect-worker.js b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/redirect-worker.js index ddcc2cf5478..82e21fc26fd 100644 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/redirect-worker.js +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/redirect-worker.js @@ -117,6 +117,13 @@ self.addEventListener('fetch', function(event) { event.respondWith(waitUntilPromise.then(async () => { if (params['sw'] == 'gen') { return Response.redirect(params['url']); + } else if (params['sw'] == 'gen-manual') { + // Note this differs from Response.redirect() in that relative URLs are + // preserved. + return new Response("", { + status: 301, + headers: {location: params['url']}, + }); } else if (params['sw'] == 'fetch') { return fetch(event.request); } else if (params['sw'] == 'fetch-url') { diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/registration-tests-basic.js b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/registration-tests-basic.js deleted file mode 100644 index eedb980a0fc..00000000000 --- a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/registration-tests-basic.js +++ /dev/null @@ -1,44 +0,0 @@ -// Basic registration tests that succeed. We don't want too many successful -// registration tests in the same file since starting a service worker can be -// slow. -function registration_tests_basic(register_method, check_error_types) { - promise_test(function(t) { - var script = 'resources/registration-worker.js'; - var scope = 'resources/registration/normal'; - return register_method(script, {scope: scope}) - .then(function(registration) { - assert_true( - registration instanceof ServiceWorkerRegistration, - 'Successfully registered.'); - return registration.unregister(); - }); - }, 'Registering normal scope'); - - promise_test(function(t) { - var script = 'resources/registration-worker.js'; - var scope = 'resources/registration/scope-with-fragment#ref'; - return register_method(script, {scope: scope}) - .then(function(registration) { - assert_true( - registration instanceof ServiceWorkerRegistration, - 'Successfully registered.'); - assert_equals( - registration.scope, - normalizeURL('resources/registration/scope-with-fragment'), - 'A fragment should be removed from scope') - return registration.unregister(); - }); - }, 'Registering scope with fragment'); - - promise_test(function(t) { - var script = 'resources/registration-worker.js'; - var scope = 'resources/'; - return register_method(script, {scope: scope}) - .then(function(registration) { - assert_true( - registration instanceof ServiceWorkerRegistration, - 'Successfully registered.'); - return registration.unregister(); - }); - }, 'Registering same scope as the script directory'); -} diff --git a/tests/wpt/web-platform-tests/service-workers/service-worker/resources/subdir/blank.html b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/subdir/blank.html new file mode 100644 index 00000000000..a3c3a4689a6 --- /dev/null +++ b/tests/wpt/web-platform-tests/service-workers/service-worker/resources/subdir/blank.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<title>Empty doc</title> diff --git a/tests/wpt/web-platform-tests/shadow-dom/nested-slot-remove-crash.html b/tests/wpt/web-platform-tests/shadow-dom/nested-slot-remove-crash.html index 3081dd6540f..d53b3b40e12 100644 --- a/tests/wpt/web-platform-tests/shadow-dom/nested-slot-remove-crash.html +++ b/tests/wpt/web-platform-tests/shadow-dom/nested-slot-remove-crash.html @@ -8,7 +8,7 @@ <span>This test passes if the renderer does not crash.</span> <script> - const root = host.attachShadow({mode:"open"}); + let root = host.attachShadow({mode:"open"}); root.innerHTML = ` <slot id=outer1 name=outer> <slot id=inner1 name=inner>Fallback</slot> @@ -27,7 +27,7 @@ // The renderer should not crash here: root.querySelector("#outer2").remove(); - const root = host.attachShadow({mode:"open"}); + root = host.attachShadow({mode:"open"}); root.innerHTML = ` <slot id=outer3> <slot id=inner3> diff --git a/tests/wpt/web-platform-tests/shadow-dom/slots-imperative-slot-api.tentative.html b/tests/wpt/web-platform-tests/shadow-dom/slots-imperative-slot-api.tentative.html index 02144640e12..2ffb08fba1b 100644 --- a/tests/wpt/web-platform-tests/shadow-dom/slots-imperative-slot-api.tentative.html +++ b/tests/wpt/web-platform-tests/shadow-dom/slots-imperative-slot-api.tentative.html @@ -15,7 +15,7 @@ test(() => { let tTree = createTestTree(test_basic); assert_not_equals(tTree.host1.attachShadow({ mode: 'open', slotAssignment: 'manual'}), null, 'slot assignment manual should work'); - assert_not_equals(tTree.host2.attachShadow({ mode: 'open', slotAssignment: 'auto'}), + assert_not_equals(tTree.host2.attachShadow({ mode: 'open', slotAssignment: 'name'}), null, 'slot assignment auto should work'); assert_throws_js(TypeError, () => { tTree.host3.attachShadow({ mode: 'open', slotAssignment: 'exceptional' })}, @@ -25,7 +25,7 @@ test(() => { <div id="test_errors"> <div id="host1"> - <template data-mode="open" data-slot-assignment="auto"> + <template data-mode="open" data-slot-assignment="name"> <slot id="s1"></slot> </template> <div id="c1"></div> diff --git a/tests/wpt/web-platform-tests/signed-exchange/resources/sxg-util.js b/tests/wpt/web-platform-tests/signed-exchange/resources/sxg-util.js index 78edf137408..22c7ec8322d 100644 --- a/tests/wpt/web-platform-tests/signed-exchange/resources/sxg-util.js +++ b/tests/wpt/web-platform-tests/signed-exchange/resources/sxg-util.js @@ -1,10 +1,12 @@ // Opens |url| in an iframe, establish a message channel with it, and waits for // a message from the frame content. Returns a promise that resolves with the -// data of the message, or rejects on 3000ms timeout. +// data of the message, or rejects on 10000ms timeout. +// If the iframe load is expected to fail, the test should have +// <meta name="timeout" content="long"> tag. function openSXGInIframeAndWaitForMessage(test_object, url, referrerPolicy) { return new Promise(async (resolve, reject) => { // We can't catch the network error on iframe. So we use the timer. - test_object.step_timeout(() => reject('timeout'), 3000); + test_object.step_timeout(() => reject('timeout'), 10000); const frame = await withIframe(url, 'sxg_iframe', referrerPolicy); const channel = new MessageChannel(); diff --git a/tests/wpt/web-platform-tests/signed-exchange/sxg-inner-url-bom.tentative.html b/tests/wpt/web-platform-tests/signed-exchange/sxg-inner-url-bom.tentative.html index 7055f5fabec..be2d7e5022f 100644 --- a/tests/wpt/web-platform-tests/signed-exchange/sxg-inner-url-bom.tentative.html +++ b/tests/wpt/web-platform-tests/signed-exchange/sxg-inner-url-bom.tentative.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <title>SignedHTTPExchange's fallback url must not have UTF-8 BOM</title> +<meta name="timeout" content="long"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/common/get-host-info.sub.js"></script> diff --git a/tests/wpt/web-platform-tests/signed-exchange/sxg-invalid-utf8-inner-url.tentative.html b/tests/wpt/web-platform-tests/signed-exchange/sxg-invalid-utf8-inner-url.tentative.html index a70bba2fdc1..7512d639e88 100644 --- a/tests/wpt/web-platform-tests/signed-exchange/sxg-invalid-utf8-inner-url.tentative.html +++ b/tests/wpt/web-platform-tests/signed-exchange/sxg-invalid-utf8-inner-url.tentative.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <title>SignedHTTPExchange's fallback url must not have invalid UTF-8 sequence</title> +<meta name="timeout" content="long"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/common/get-host-info.sub.js"></script> diff --git a/tests/wpt/web-platform-tests/signed-exchange/sxg-merkle-integrity-error.tentative.html b/tests/wpt/web-platform-tests/signed-exchange/sxg-merkle-integrity-error.tentative.html index 5f923d20542..a5c4a2f7eec 100644 --- a/tests/wpt/web-platform-tests/signed-exchange/sxg-merkle-integrity-error.tentative.html +++ b/tests/wpt/web-platform-tests/signed-exchange/sxg-merkle-integrity-error.tentative.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <title>SignedHTTPExchange with payload integrity error</title> +<meta name="timeout" content="long"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/common/get-host-info.sub.js"></script> diff --git a/tests/wpt/web-platform-tests/signed-exchange/sxg-referrer-same-physical-remote-logical.tentative.https.html b/tests/wpt/web-platform-tests/signed-exchange/sxg-referrer-same-physical-remote-logical.tentative.https.html index fc6f187be38..3ba597ed496 100644 --- a/tests/wpt/web-platform-tests/signed-exchange/sxg-referrer-same-physical-remote-logical.tentative.https.html +++ b/tests/wpt/web-platform-tests/signed-exchange/sxg-referrer-same-physical-remote-logical.tentative.https.html @@ -1,5 +1,6 @@ <!DOCTYPE html> <title>Referrer of SignedHTTPExchange(physical:same origin, logical:remote origin)</title> +<meta name="timeout" content="long"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="/common/get-host-info.sub.js"></script> diff --git a/tests/wpt/web-platform-tests/streams/readable-streams/async-iterator.any.js b/tests/wpt/web-platform-tests/streams/readable-streams/async-iterator.any.js index 7c49fe937de..7669a35d9a7 100644 --- a/tests/wpt/web-platform-tests/streams/readable-streams/async-iterator.any.js +++ b/tests/wpt/web-platform-tests/streams/readable-streams/async-iterator.any.js @@ -625,3 +625,26 @@ for (const preventCancel of [false, true]) { rs.getReader(); }, `return() should unlock the stream synchronously when preventCancel = ${preventCancel}`); } + +promise_test(async () => { + const rs = new ReadableStream({ + async start(c) { + c.enqueue('a'); + c.enqueue('b'); + c.enqueue('c'); + await flushAsyncEvents(); + // At this point, the async iterator has a read request in the stream's queue for its pending next() promise. + // Closing the stream now causes two things to happen *synchronously*: + // 1. ReadableStreamClose resolves reader.[[closedPromise]] with undefined. + // 2. ReadableStreamClose calls the read request's close steps, which calls ReadableStreamReaderGenericRelease, + // which replaces reader.[[closedPromise]] with a rejected promise. + c.close(); + } + }); + + const chunks = []; + for await (const chunk of rs) { + chunks.push(chunk); + } + assert_array_equals(chunks, ['a', 'b', 'c']); +}, 'close() while next() is pending'); diff --git a/tests/wpt/web-platform-tests/streams/readable-streams/default-reader.any.js b/tests/wpt/web-platform-tests/streams/readable-streams/default-reader.any.js index fc55ebc5b8f..60c740a8288 100644 --- a/tests/wpt/web-platform-tests/streams/readable-streams/default-reader.any.js +++ b/tests/wpt/web-platform-tests/streams/readable-streams/default-reader.any.js @@ -154,6 +154,57 @@ promise_test(t => { }, 'closed should be rejected after reader releases its lock (multiple stream locks)'); +promise_test(t => { + + let controller; + const rs = new ReadableStream({ + start(c) { + controller = c; + } + }); + + const reader = rs.getReader(); + const promise1 = reader.closed; + + controller.close(); + + reader.releaseLock(); + const promise2 = reader.closed; + + assert_not_equals(promise1, promise2, '.closed should be replaced'); + return Promise.all([ + promise1, + promise_rejects_js(t, TypeError, promise2, '.closed after releasing lock'), + ]); + +}, 'closed is replaced when stream closes and reader releases its lock'); + +promise_test(t => { + + const theError = { name: 'unique error' }; + let controller; + const rs = new ReadableStream({ + start(c) { + controller = c; + } + }); + + const reader = rs.getReader(); + const promise1 = reader.closed; + + controller.error(theError); + + reader.releaseLock(); + const promise2 = reader.closed; + + assert_not_equals(promise1, promise2, '.closed should be replaced'); + return Promise.all([ + promise_rejects_exactly(t, theError, promise1, '.closed before releasing lock'), + promise_rejects_js(t, TypeError, promise2, '.closed after releasing lock') + ]); + +}, 'closed is replaced when stream errors and reader releases its lock'); + promise_test(() => { const rs = new ReadableStream({ diff --git a/tests/wpt/web-platform-tests/streams/readable-streams/general.any.js b/tests/wpt/web-platform-tests/streams/readable-streams/general.any.js index 86b793fe16c..4e54990823a 100644 --- a/tests/wpt/web-platform-tests/streams/readable-streams/general.any.js +++ b/tests/wpt/web-platform-tests/streams/readable-streams/general.any.js @@ -210,14 +210,14 @@ promise_test(() => { assert_unreached('closed should be rejected'); }, e => { closed = true; - assert_true(read); + assert_false(read); assert_equals(e, error, 'closed should be rejected with the thrown error'); }), reader.read().then(() => { assert_unreached('read() should be rejected'); }, e => { read = true; - assert_false(closed); + assert_true(closed); assert_equals(e, error, 'read() should be rejected with the thrown error'); }) ]); diff --git a/tests/wpt/web-platform-tests/svg/animations/repeatcount-numeric-limit.tentative.svg b/tests/wpt/web-platform-tests/svg/animations/repeatcount-numeric-limit.tentative.svg new file mode 100644 index 00000000000..aa0432559bf --- /dev/null +++ b/tests/wpt/web-platform-tests/svg/animations/repeatcount-numeric-limit.tentative.svg @@ -0,0 +1,26 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:h="http://www.w3.org/1999/xhtml"> + <title>A huge 'repeatCount' (1e+309) is treated as unspecified</title> + <h:link rel="help" href="https://svgwg.org/specs/animations/#TimingAttributes"/> + <h:script src="/resources/testharness.js"/> + <h:script src="/resources/testharnessreport.js"/> + + <rect width="50" height="100" fill="blue"> + <animate attributeName="fill" from="#007f00" to="green" dur="10ms" fill="freeze" + repeatCount="1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"/> + </rect> + <rect width="50" height="100" fill="blue" x="50"> + <animate attributeName="fill" from="#007f00" to="green" dur="10ms" fill="freeze" + repeatCount="1e+309"/> + </rect> + <script> + promise_test(t => { + let watchers = Array.from(document.getElementsByTagName('animate')).map(element => { + let watcher = new EventWatcher(t, element, ['endEvent', 'repeatEvent']); + return watcher.wait_for('endEvent').then(() => { + assert_equals(getComputedStyle(element).fill, 'rgb(0, 128, 0)'); + }); + }); + return Promise.all(watchers); + }); + </script> +</svg> diff --git a/tests/wpt/web-platform-tests/svg/styling/font-size-number-calc-crash.svg b/tests/wpt/web-platform-tests/svg/styling/font-size-number-calc-crash.svg new file mode 100644 index 00000000000..6b56d9df2b4 --- /dev/null +++ b/tests/wpt/web-platform-tests/svg/styling/font-size-number-calc-crash.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" font-size="calc(0)"></svg> +<!-- Loading this test should not cause a crash. https://crbug.com/1172750 --> diff --git a/tests/wpt/web-platform-tests/tools/ci/azure/affected_tests.yml b/tests/wpt/web-platform-tests/tools/ci/azure/affected_tests.yml index 494ff1efa19..96ab2b8cc52 100644 --- a/tests/wpt/web-platform-tests/tools/ci/azure/affected_tests.yml +++ b/tests/wpt/web-platform-tests/tools/ci/azure/affected_tests.yml @@ -15,7 +15,7 @@ steps: - template: install_safari.yml - template: update_hosts.yml - template: update_manifest.yml -- script: ./wpt run --yes --no-pause --no-fail-on-unexpected --no-restart-on-unexpected --affected ${{ parameters.affectedRange }} --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report.json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot.txt --channel preview safari +- script: ./wpt run --yes --no-pause --no-fail-on-unexpected --no-restart-on-unexpected --affected ${{ parameters.affectedRange }} --log-wptreport $(Build.ArtifactStagingDirectory)/wpt_report.json --log-wptscreenshot $(Build.ArtifactStagingDirectory)/wpt_screenshot.txt --channel preview --kill-safari safari displayName: 'Run tests' - task: PublishBuildArtifacts@1 displayName: 'Publish results' diff --git a/tests/wpt/web-platform-tests/tools/ci/azure/install_safari.yml b/tests/wpt/web-platform-tests/tools/ci/azure/install_safari.yml index 1636b8e7474..d929ffa4da9 100644 --- a/tests/wpt/web-platform-tests/tools/ci/azure/install_safari.yml +++ b/tests/wpt/web-platform-tests/tools/ci/azure/install_safari.yml @@ -8,6 +8,7 @@ steps: condition: eq(variables['safaridriver_diagnose'], true) - ${{ if eq(parameters.channel, 'preview') }}: - script: | + set -eux HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask tools/ci/azure/safari-technology-preview.rb # Workaround for `sudo safardriver --enable` not working on Catalina: # https://github.com/web-platform-tests/wpt/issues/21751 @@ -18,6 +19,7 @@ steps: displayName: 'Install Safari Technology Preview' - ${{ if eq(parameters.channel, 'stable') }}: - script: | + set -eux sudo safaridriver --enable defaults write com.apple.Safari WebKitJavaScriptCanOpenWindowsAutomatically 1 displayName: 'Configure Safari' diff --git a/tests/wpt/web-platform-tests/tools/ci/azure/safari-technology-preview.rb b/tests/wpt/web-platform-tests/tools/ci/azure/safari-technology-preview.rb index 7149e6b2f0f..046b336cbdc 100644 --- a/tests/wpt/web-platform-tests/tools/ci/azure/safari-technology-preview.rb +++ b/tests/wpt/web-platform-tests/tools/ci/azure/safari-technology-preview.rb @@ -1,10 +1,10 @@ cask "safari-technology-preview" do if MacOS.version <= :catalina - version "119,001-97793-20210126-7aa49ced-c2f2-415d-a564-6fab25fcb8e6" - sha256 "f5ca6acc23d65e23aaf0df5ff35fb3e28c9ae7a40e46331122d29a3e788ca2c1" + version "120,071-05417-20210210-20164467-7abe-4a43-900d-233abd8aa93e" + sha256 "b0f923815a524baf6a58dfd7786f9965a4d78954dcfe2eed27eda6a21b9e01df" else - version "119,001-98875-20210126-7699f76e-e3a5-4f45-80f9-8975c236d337" - sha256 "048e5ac1e761df757075134fa3d1f80ae7da56d5eef2511970aeac34f5ed0027" + version "120,071-05422-20210210-6651b9d4-a687-4e72-922b-76e7809f1aa7" + sha256 "aee083fb1e0e956f27017be505a6d835fdb767ac14470e263ab8782452b92db0" end url "https://secure-appldnld.apple.com/STP/#{version.after_comma}/SafariTechnologyPreview.dmg" diff --git a/tests/wpt/web-platform-tests/tools/ci/jobs.py b/tests/wpt/web-platform-tests/tools/ci/jobs.py index 7624eb0d516..430c32ec6d4 100644 --- a/tests/wpt/web-platform-tests/tools/ci/jobs.py +++ b/tests/wpt/web-platform-tests/tools/ci/jobs.py @@ -4,7 +4,6 @@ import re from ..wpt.testfiles import branch_point, files_changed from tools import localpaths # noqa: F401 -from six import iteritems wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) @@ -109,7 +108,7 @@ def get_jobs(paths, **kwargs): includes = kwargs.get("includes") if includes is not None: includes = set(includes) - for key, value in iteritems(job_path_map): + for key, value in job_path_map.items(): if includes is None or key in includes: rules[key] = Ruleset(value) @@ -124,7 +123,7 @@ def get_jobs(paths, **kwargs): # Default jobs shuld run even if there were no changes if not paths: - for job, path_re in iteritems(job_path_map): + for job, path_re in job_path_map.items(): if ".*" in path_re: jobs.add(job) diff --git a/tests/wpt/web-platform-tests/tools/ci/run_tc.py b/tests/wpt/web-platform-tests/tools/ci/run_tc.py index 69bf72fb745..b7e5d72146a 100755 --- a/tests/wpt/web-platform-tests/tools/ci/run_tc.py +++ b/tests/wpt/web-platform-tests/tools/ci/run_tc.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Wrapper script for running jobs in Taskcluster @@ -58,6 +58,8 @@ def run(cmd, return_stdout=False, **kwargs): print(" ".join(cmd)) if return_stdout: f = subprocess.check_output + if "encoding" not in kwargs: + kwargs["encoding"] = "utf-8" else: f = subprocess.check_call return f(cmd, **kwargs) @@ -146,7 +148,7 @@ def install_chrome(channel): dest = os.path.join("/tmp", deb_archive) deb_url = "https://dl.google.com/linux/direct/%s" % deb_archive - with open(dest, "w") as f: + with open(dest, "wb") as f: get_download_to_descriptor(f, deb_url) run(["sudo", "apt-get", "-qqy", "update"]) diff --git a/tests/wpt/web-platform-tests/tools/ci/tc/decision.py b/tests/wpt/web-platform-tests/tools/ci/tc/decision.py index b089077506c..d19ff7692cf 100644 --- a/tests/wpt/web-platform-tests/tools/ci/tc/decision.py +++ b/tests/wpt/web-platform-tests/tools/ci/tc/decision.py @@ -7,7 +7,6 @@ import subprocess from collections import OrderedDict import taskcluster -from six import iteritems, itervalues from . import taskgraph @@ -48,7 +47,7 @@ def fetch_event_data(queue): def filter_triggers(event, all_tasks): is_pr, branch = get_triggers(event) triggered = OrderedDict() - for name, task in iteritems(all_tasks): + for name, task in all_tasks.items(): if "trigger" in task: if is_pr and "pull-request" in task["trigger"]: triggered[name] = task @@ -104,7 +103,7 @@ def get_extra_jobs(event): def filter_schedule_if(event, tasks): scheduled = OrderedDict() run_jobs = None - for name, task in iteritems(tasks): + for name, task in tasks.items(): if "schedule-if" in task: if "run-job" in task["schedule-if"]: if run_jobs is None: @@ -283,7 +282,7 @@ def build_task_graph(event, all_tasks, tasks): env_extra=env_extra) task_id_map[task_name] = (task_id, task_data) - for task_name, task in iteritems(tasks): + for task_name, task in tasks.items(): if task_name == "sink-task": # sink-task will be created below at the end of the ordered dict, # so that it can depend on all other tasks. @@ -309,7 +308,7 @@ def build_task_graph(event, all_tasks, tasks): def create_tasks(queue, task_id_map): - for (task_id, task_data) in itervalues(task_id_map): + for (task_id, task_data) in task_id_map.values(): queue.createTask(task_id, task_data) diff --git a/tests/wpt/web-platform-tests/tools/ci/tc/taskgraph.py b/tests/wpt/web-platform-tests/tools/ci/tc/taskgraph.py index 8c78f7b5dbe..d5c889d1fb9 100644 --- a/tests/wpt/web-platform-tests/tools/ci/tc/taskgraph.py +++ b/tests/wpt/web-platform-tests/tools/ci/tc/taskgraph.py @@ -4,9 +4,7 @@ import re from collections import OrderedDict from copy import deepcopy -import six import yaml -from six import iteritems here = os.path.dirname(__file__) @@ -27,7 +25,7 @@ def load_task_file(path): def update_recursive(data, update_data): - for key, value in iteritems(update_data): + for key, value in update_data.items(): if key not in data: data[key] = value else: @@ -94,13 +92,13 @@ def replace_vars(input_string, variables): def sub_variables(data, variables): - if isinstance(data, six.string_types): + if isinstance(data, str): return replace_vars(data, variables) if isinstance(data, list): return [sub_variables(item, variables) for item in data] if isinstance(data, dict): return {key: sub_variables(value, variables) - for key, value in iteritems(data)} + for key, value in data.items()} return data @@ -154,7 +152,7 @@ def load_tasks(tasks_data): raise ValueError("Got duplicate task name %s" % new_name) map_resolved_tasks[new_name] = substitute_variables(data) - for task_default_name, data in iteritems(map_resolved_tasks): + for task_default_name, data in map_resolved_tasks.items(): task = resolve_use(data, tasks_data["components"]) task = resolve_name(task, task_default_name) tasks.extend(resolve_chunks(task)) diff --git a/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_decision.py b/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_decision.py index 1dcc35cf694..2f9018a4e0b 100644 --- a/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_decision.py +++ b/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_decision.py @@ -1,13 +1,5 @@ import mock import pytest -import os -import sys - -from six import iteritems - -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..", "..")) -sys.path.insert(0, root) from tools.ci.tc import decision @@ -49,7 +41,7 @@ def test_extra_jobs_pr(msg, expected, event): """Copy obj, except if it's a string with the value <message> replace it with the value of the msg argument""" if isinstance(obj, dict): - return {key: sub(value) for (key, value) in iteritems(obj)} + return {key: sub(value) for (key, value) in obj.items()} elif isinstance(obj, list): return [sub(value) for value in obj] elif obj == "<message>": diff --git a/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_taskgraph.py b/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_taskgraph.py index 5850932fa0c..dd8535bb647 100644 --- a/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_taskgraph.py +++ b/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_taskgraph.py @@ -1,12 +1,6 @@ -import os import pytest -import sys import yaml -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..", "..")) -sys.path.insert(0, root) - from tools.ci.tc import taskgraph @pytest.mark.parametrize("data, update_data, expected", [ diff --git a/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_valid.py b/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_valid.py index 9891e1cfc5d..d489205b26f 100644 --- a/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_valid.py +++ b/tests/wpt/web-platform-tests/tools/ci/tc/tests/test_valid.py @@ -1,7 +1,6 @@ import json import os from io import open -import sys import jsone import mock @@ -10,11 +9,10 @@ import requests import yaml from jsonschema import validate +from tools.ci.tc import decision + here = os.path.dirname(__file__) root = os.path.abspath(os.path.join(here, "..", "..", "..", "..")) -sys.path.insert(0, root) - -from tools.ci.tc import decision def data_path(filename): diff --git a/tests/wpt/web-platform-tests/tools/ci/tests/test_jobs.py b/tests/wpt/web-platform-tests/tools/ci/tests/test_jobs.py index 96ebe8bf289..aa12738204f 100644 --- a/tests/wpt/web-platform-tests/tools/ci/tests/test_jobs.py +++ b/tests/wpt/web-platform-tests/tools/ci/tests/test_jobs.py @@ -1,10 +1,3 @@ -import os -import sys - -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - from tools.ci import jobs all_jobs = { diff --git a/tests/wpt/web-platform-tests/tools/docker/frontend.py b/tests/wpt/web-platform-tests/tools/docker/frontend.py index 6d35d4c3826..c05937b73dc 100644 --- a/tests/wpt/web-platform-tests/tools/docker/frontend.py +++ b/tests/wpt/web-platform-tests/tools/docker/frontend.py @@ -5,8 +5,6 @@ import re import subprocess import sys -from six import iteritems - here = os.path.abspath(os.path.dirname(__file__)) wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir)) @@ -37,7 +35,7 @@ def walk_yaml(root, target): if isinstance(value, (dict, list)): rv.extend(walk_yaml(value, target)) elif isinstance(root, dict): - for key, value in iteritems(root): + for key, value in root.items(): if isinstance(value, (dict, list)): rv.extend(walk_yaml(value, target)) elif key == target: diff --git a/tests/wpt/web-platform-tests/tools/gitignore/gitignore.py b/tests/wpt/web-platform-tests/tools/gitignore/gitignore.py index dbe83c27eee..500fe7835bd 100644 --- a/tests/wpt/web-platform-tests/tools/gitignore/gitignore.py +++ b/tests/wpt/web-platform-tests/tools/gitignore/gitignore.py @@ -1,7 +1,7 @@ import re import os import itertools -from six import ensure_binary, itervalues, iteritems +from six import ensure_binary from collections import defaultdict MYPY = False @@ -194,13 +194,13 @@ class PathFilter(object): rule = cast(Tuple[bool, Pattern[bytes]], rule) if not dir_only: rules_iter = itertools.chain( - itertools.chain(*(iteritems(item) for item in itervalues(self.literals_dir))), - itertools.chain(*(iteritems(item) for item in itervalues(self.literals_file))), + itertools.chain(*(item.items() for item in self.literals_dir.values())), + itertools.chain(*(item.items() for item in self.literals_file.values())), self.patterns_dir, self.patterns_file) # type: Iterable[Tuple[Any, List[Tuple[bool, Pattern[bytes]]]]] else: rules_iter = itertools.chain( - itertools.chain(*(iteritems(item) for item in itervalues(self.literals_dir))), + itertools.chain(*(item.items() for item in self.literals_dir.values())), self.patterns_dir) for rules in rules_iter: diff --git a/tests/wpt/web-platform-tests/tools/lint/lint.py b/tests/wpt/web-platform-tests/tools/lint/lint.py index 1872e627259..d8fb9fc35ef 100644 --- a/tests/wpt/web-platform-tests/tools/lint/lint.py +++ b/tests/wpt/web-platform-tests/tools/lint/lint.py @@ -14,6 +14,7 @@ import sys import tempfile from collections import defaultdict +from urllib.parse import urlsplit, urljoin from . import fnmatch from . import rules @@ -24,9 +25,7 @@ from ..wpt import testfiles from ..manifest.vcs import walk from ..manifest.sourcefile import SourceFile, js_meta_re, python_meta_re, space_chars, get_any_variants -from six import binary_type, ensure_binary, ensure_text, iteritems, itervalues, with_metaclass -from six.moves import range -from six.moves.urllib.parse import urlsplit, urljoin +from six import ensure_binary, ensure_text MYPY = False if MYPY: @@ -325,7 +324,7 @@ def check_css_globally_unique(repo_root, paths): errors = [] - for name, colliding in iteritems(test_files): + for name, colliding in test_files.items(): if len(colliding) > 1: if not _all_files_equal([os.path.join(repo_root, x) for x in colliding]): # Only compute by_spec if there are prima-facie collisions because of cost @@ -342,7 +341,7 @@ def check_css_globally_unique(repo_root, paths): continue by_spec[spec].add(path) - for spec, spec_paths in iteritems(by_spec): + for spec, spec_paths in by_spec.items(): if not _all_files_equal([os.path.join(repo_root, x) for x in spec_paths]): for x in spec_paths: context1 = (name, spec, ", ".join(sorted(spec_paths))) @@ -351,7 +350,7 @@ def check_css_globally_unique(repo_root, paths): for rule_class, d in [(rules.CSSCollidingRefName, ref_files), (rules.CSSCollidingSupportName, support_files)]: - for name, colliding in iteritems(d): + for name, colliding in d.items(): if len(colliding) > 1: if not _all_files_equal([os.path.join(repo_root, x) for x in colliding]): context2 = (name, ", ".join(sorted(colliding))) @@ -451,7 +450,7 @@ def filter_ignorelist_errors(data, errors): # which explains how to fix it correctly and shouldn't be skipped. if error_type in data and error_type != "IGNORED PATH": wl_files = data[error_type] - for file_match, allowed_lines in iteritems(wl_files): + for file_match, allowed_lines in wl_files.items(): if None in allowed_lines or line in allowed_lines: if fnmatch.fnmatchcase(normpath, file_match): skipped[i] = True @@ -507,6 +506,7 @@ def check_parsed(repo_root, path, f): if (source_file.type != "support" and not source_file.name_is_reference and not source_file.name_is_tentative and + not source_file.name_is_crashtest and not source_file.spec_links): return [rules.MissingLink.error(path)] @@ -667,7 +667,7 @@ def check_parsed(repo_root, path, f): return errors -class ASTCheck(with_metaclass(abc.ABCMeta)): +class ASTCheck(metaclass=abc.ABCMeta): @abc.abstractproperty def rule(self): # type: () -> Type[rules.Rule] @@ -740,7 +740,7 @@ def check_script_metadata(repo_root, path, f): done = False errors = [] for idx, line in enumerate(f): - assert isinstance(line, binary_type), line + assert isinstance(line, bytes), line m = meta_re.match(line) if m: @@ -974,7 +974,7 @@ def create_parser(): def main(**kwargs_str): # type: (**Any) -> int - kwargs = {ensure_text(key): value for key, value in iteritems(kwargs_str)} + kwargs = {ensure_text(key): value for key, value in kwargs_str.items()} assert logger is not None if kwargs.get("json") and kwargs.get("markdown"): @@ -1102,7 +1102,7 @@ def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_output if error_count and github_checks_outputter: github_checks_outputter.output("```") - return sum(itervalues(error_count)) + return sum(error_count.values()) path_lints = [check_file_type, check_path_length, check_worker_collision, check_ahem_copy, diff --git a/tests/wpt/web-platform-tests/tools/lint/rules.py b/tests/wpt/web-platform-tests/tools/lint/rules.py index b389e3d8f08..e9bb30b59cf 100644 --- a/tests/wpt/web-platform-tests/tools/lint/rules.py +++ b/tests/wpt/web-platform-tests/tools/lint/rules.py @@ -5,8 +5,6 @@ import inspect import os import re -import six - MYPY = False if MYPY: # MYPY is set to True when run under Mypy. @@ -19,7 +17,7 @@ def collapse(text): return inspect.cleandoc(str(text)).replace("\n", " ") -class Rule(six.with_metaclass(abc.ABCMeta)): +class Rule(metaclass=abc.ABCMeta): @abc.abstractproperty def name(self): # type: () -> Text @@ -367,7 +365,7 @@ class TentativeDirectoryName(Rule): to_fix = "rename directory to be called 'tentative'" -class Regexp(six.with_metaclass(abc.ABCMeta)): +class Regexp(metaclass=abc.ABCMeta): @abc.abstractproperty def pattern(self): # type: () -> bytes diff --git a/tests/wpt/web-platform-tests/tools/lint/tests/base.py b/tests/wpt/web-platform-tests/tools/lint/tests/base.py index 360f13ce293..36c16676d52 100644 --- a/tests/wpt/web-platform-tests/tools/lint/tests/base.py +++ b/tests/wpt/web-platform-tests/tools/lint/tests/base.py @@ -1,11 +1,10 @@ from __future__ import unicode_literals -from six import integer_types, text_type def check_errors(errors): for e in errors: error_type, description, path, line_number = e - assert isinstance(error_type, text_type) - assert isinstance(description, text_type) - assert isinstance(path, text_type) - assert line_number is None or isinstance(line_number, integer_types) + assert isinstance(error_type, str) + assert isinstance(description, str) + assert isinstance(path, str) + assert line_number is None or isinstance(line_number, int) diff --git a/tests/wpt/web-platform-tests/tools/lint/tests/test_file_lints.py b/tests/wpt/web-platform-tests/tools/lint/tests/test_file_lints.py index be95890d6fb..d892866fed7 100644 --- a/tests/wpt/web-platform-tests/tools/lint/tests/test_file_lints.py +++ b/tests/wpt/web-platform-tests/tools/lint/tests/test_file_lints.py @@ -1,10 +1,8 @@ -from __future__ import unicode_literals - from ..lint import check_file_contents from .base import check_errors +import io import os import pytest -import six INTERESTING_FILE_NAMES = { "python": [ @@ -26,7 +24,7 @@ INTERESTING_FILE_NAMES = { def check_with_files(input_bytes): return { - filename: (check_file_contents("", filename, six.BytesIO(input_bytes)), kind) + filename: (check_file_contents("", filename, io.BytesIO(input_bytes)), kind) for (filename, kind) in ( (os.path.join("html", filename), kind) @@ -660,28 +658,6 @@ def test_late_timeout(): ] -# Note: This test checks the print *statement* (which doesn't exist in python 3). -# The print *function* is checked in test_print_function below. -@pytest.mark.skipif(six.PY3, reason="Cannot parse print statements from python 3") -def test_print_statement(): - error_map = check_with_files(b"def foo():\n print 'statement'\n print\n") - - for (filename, (errors, kind)) in error_map.items(): - check_errors(errors) - - if kind == "python": - assert errors == [ - ("PRINT STATEMENT", "A server-side python support file contains a `print` statement", filename, 2), - ("PRINT STATEMENT", "A server-side python support file contains a `print` statement", filename, 3), - ] - elif kind == "web-strict": - assert errors == [ - ("PARSE-FAILED", "Unable to parse file", filename, None), - ] - else: - assert errors == [] - - def test_print_function(): error_map = check_with_files(b"def foo():\n print('function')\n") @@ -760,7 +736,7 @@ def fifth(): def test_open_mode(): for method in ["open", "file"]: code = open_mode_code.format(method).encode("utf-8") - errors = check_file_contents("", "test.py", six.BytesIO(code)) + errors = check_file_contents("", "test.py", io.BytesIO(code)) check_errors(errors) message = ("File opened without providing an explicit mode (note: " + @@ -779,7 +755,7 @@ def test_open_mode(): ("css/bar.html", True), ]) def test_css_support_file(filename, expect_error): - errors = check_file_contents("", filename, six.BytesIO(b"")) + errors = check_file_contents("", filename, io.BytesIO(b"")) check_errors(errors) if expect_error: @@ -800,7 +776,7 @@ def test_css_missing_file_in_css(): <script src="/resources/testharnessreport.js"></script> </html> """ - errors = check_file_contents("", "css/foo/bar.html", six.BytesIO(code)) + errors = check_file_contents("", "css/foo/bar.html", io.BytesIO(code)) check_errors(errors) assert errors == [ @@ -812,7 +788,7 @@ def test_css_missing_file_in_css(): def test_css_missing_file_manual(): - errors = check_file_contents("", "css/foo/bar-manual.html", six.BytesIO(b"")) + errors = check_file_contents("", "css/foo/bar-manual.html", io.BytesIO(b"")) check_errors(errors) assert errors == [ @@ -833,7 +809,7 @@ def test_css_missing_file_tentative(): # The tentative flag covers tests that make assertions 'not yet required by # any specification', so they need not have a specification link. - errors = check_file_contents("", "css/foo/bar.tentative.html", six.BytesIO(code)) + errors = check_file_contents("", "css/foo/bar.tentative.html", io.BytesIO(code)) assert not errors @@ -861,7 +837,7 @@ def test_css_missing_file_tentative(): (b"""// META: timeout=bar\n""", (1, "UNKNOWN-TIMEOUT-METADATA")), ]) def test_script_metadata(filename, input, error): - errors = check_file_contents("", filename, six.BytesIO(input)) + errors = check_file_contents("", filename, io.BytesIO(input)) check_errors(errors) if error is not None: @@ -902,7 +878,7 @@ def test_script_metadata(filename, input, error): def test_script_globals_metadata(globals, error): filename = "foo.any.js" input = b"""// META: global=%s\n""" % globals - errors = check_file_contents("", filename, six.BytesIO(input)) + errors = check_file_contents("", filename, io.BytesIO(input)) check_errors(errors) if error is not None: @@ -933,7 +909,7 @@ def test_script_globals_metadata(globals, error): ]) def test_python_metadata(input, error): filename = "test.py" - errors = check_file_contents("", filename, six.BytesIO(input)) + errors = check_file_contents("", filename, io.BytesIO(input)) check_errors(errors) if error is not None: diff --git a/tests/wpt/web-platform-tests/tools/lint/tests/test_lint.py b/tests/wpt/web-platform-tests/tools/lint/tests/test_lint.py index 798dd7b9431..e89499fe9be 100644 --- a/tests/wpt/web-platform-tests/tools/lint/tests/test_lint.py +++ b/tests/wpt/web-platform-tests/tools/lint/tests/test_lint.py @@ -1,18 +1,12 @@ -from __future__ import unicode_literals - +import io import os import sys import mock -import six - -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) -from tools.localpaths import repo_root -from tools.lint import lint as lint_mod -from tools.lint.lint import filter_ignorelist_errors, parse_ignorelist, lint, create_parser +from ...localpaths import repo_root +from .. import lint as lint_mod +from ..lint import filter_ignorelist_errors, parse_ignorelist, lint, create_parser _dummy_repo = os.path.join(os.path.dirname(__file__), "dummy") @@ -58,7 +52,7 @@ def test_filter_ignorelist_errors(): def test_parse_ignorelist(): - input_buffer = six.StringIO(""" + input_buffer = io.StringIO(""" # Comment CR AT EOL: svg/import/* CR AT EOL: streams/resources/test-utils.js diff --git a/tests/wpt/web-platform-tests/tools/localpaths.py b/tests/wpt/web-platform-tests/tools/localpaths.py index a79acb82a50..1cb6f5b3724 100644 --- a/tests/wpt/web-platform-tests/tools/localpaths.py +++ b/tests/wpt/web-platform-tests/tools/localpaths.py @@ -18,6 +18,7 @@ sys.path.insert(0, os.path.join(here, "third_party", "pluggy", "src")) sys.path.insert(0, os.path.join(here, "third_party", "py")) sys.path.insert(0, os.path.join(here, "third_party", "pytest")) sys.path.insert(0, os.path.join(here, "third_party", "pytest", "src")) +sys.path.insert(0, os.path.join(here, "third_party", "pytest-asyncio")) sys.path.insert(0, os.path.join(here, "third_party", "six")) sys.path.insert(0, os.path.join(here, "third_party", "webencodings")) sys.path.insert(0, os.path.join(here, "third_party", "h2")) @@ -25,6 +26,8 @@ sys.path.insert(0, os.path.join(here, "third_party", "hpack")) sys.path.insert(0, os.path.join(here, "third_party", "hyperframe")) sys.path.insert(0, os.path.join(here, "third_party", "certifi")) sys.path.insert(0, os.path.join(here, "third_party", "hyper")) +sys.path.insert(0, os.path.join(here, "third_party", "websockets", "src")) +sys.path.insert(0, os.path.join(here, "third_party", "iniconfig", "src")) if sys.version_info < (3, 8): sys.path.insert(0, os.path.join(here, "third_party", "importlib_metadata")) sys.path.insert(0, os.path.join(here, "webdriver")) diff --git a/tests/wpt/web-platform-tests/tools/manifest/XMLParser.py b/tests/wpt/web-platform-tests/tools/manifest/XMLParser.py index 80aa3b5b920..45a8a54029c 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/XMLParser.py +++ b/tests/wpt/web-platform-tests/tools/manifest/XMLParser.py @@ -6,8 +6,6 @@ from collections import OrderedDict from xml.parsers import expat import xml.etree.ElementTree as etree # noqa: N813 -from six import text_type - MYPY = False if MYPY: # MYPY is set to True when run under Mypy. @@ -83,7 +81,7 @@ class XMLParser(object): def _start(self, tag, attrib_in): # type: (Text, List[str]) -> etree.Element - assert isinstance(tag, text_type) + assert isinstance(tag, str) self._fed_data = None tag = _fixname(tag) attrib = OrderedDict() # type: Dict[Union[bytes, Text], Union[bytes, Text]] diff --git a/tests/wpt/web-platform-tests/tools/manifest/download.py b/tests/wpt/web-platform-tests/tools/manifest/download.py index 9d763181d82..88d478fe0ef 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/download.py +++ b/tests/wpt/web-platform-tests/tools/manifest/download.py @@ -7,8 +7,7 @@ import json import io import os from datetime import datetime, timedelta - -from six.moves.urllib.request import urlopen +from urllib.request import urlopen try: import zstandard diff --git a/tests/wpt/web-platform-tests/tools/manifest/item.py b/tests/wpt/web-platform-tests/tools/manifest/item.py index 857c8485dd0..4b973d5399a 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/item.py +++ b/tests/wpt/web-platform-tests/tools/manifest/item.py @@ -1,7 +1,6 @@ import os.path from inspect import isabstract -from six import iteritems, with_metaclass -from six.moves.urllib.parse import urljoin, urlparse, parse_qs +from urllib.parse import urljoin, urlparse, parse_qs from abc import ABCMeta, abstractproperty from .utils import to_os_path @@ -42,7 +41,7 @@ class ManifestItemMeta(ABCMeta): return rv # type: ignore -class ManifestItem(with_metaclass(ManifestItemMeta)): +class ManifestItem(metaclass=ManifestItemMeta): __slots__ = ("_tests_root", "path") def __init__(self, tests_root, path): @@ -289,7 +288,7 @@ class RefTest(URLManifestItem): if self.dpi is not None: extras["dpi"] = self.dpi if self.fuzzy: - extras["fuzzy"] = list(iteritems(self.fuzzy)) + extras["fuzzy"] = list(self.fuzzy.items()) return rv @classmethod diff --git a/tests/wpt/web-platform-tests/tools/manifest/jsonlib.py b/tests/wpt/web-platform-tests/tools/manifest/jsonlib.py index e7f07c3ad2a..49eaf02e800 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/jsonlib.py +++ b/tests/wpt/web-platform-tests/tools/manifest/jsonlib.py @@ -1,8 +1,6 @@ import re import json -from six import PY3 - MYPY = False if MYPY: @@ -49,11 +47,9 @@ _ujson_dump_local_kwargs = { 'ensure_ascii': False, 'escape_forward_slashes': False, 'indent': 1, + 'reject_bytes': True, } # type: Dict[str, Any] -if PY3: - _ujson_dump_local_kwargs['reject_bytes'] = True - _json_dump_local_kwargs = { 'ensure_ascii': False, @@ -99,10 +95,9 @@ else: _ujson_dump_dist_kwargs = { 'sort_keys': True, 'indent': 1, + 'reject_bytes': True, } # type: Dict[str, Any] -if PY3: - _ujson_dump_dist_kwargs['reject_bytes'] = True _json_dump_dist_kwargs = { 'sort_keys': True, diff --git a/tests/wpt/web-platform-tests/tools/manifest/manifest.py b/tests/wpt/web-platform-tests/tools/manifest/manifest.py index 123f0451874..9f1e902d31a 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/manifest.py +++ b/tests/wpt/web-platform-tests/tools/manifest/manifest.py @@ -1,17 +1,10 @@ import io -import itertools import os import sys from atomicwrites import atomic_write from copy import deepcopy from multiprocessing import Pool, cpu_count -from six import ( - PY3, - ensure_text, - iteritems, - itervalues, - string_types, -) +from six import ensure_text from . import jsonlib from . import vcs @@ -93,7 +86,7 @@ class ManifestData(ManifestDataType): """Dictionary subclass containing a TypeData instance for each test type, keyed by type name""" self.initialized = False # type: bool - for key, value in iteritems(item_classes): + for key, value in item_classes.items(): self[key] = TypeData(manifest, value) self.initialized = True self.json_obj = None # type: None @@ -109,7 +102,7 @@ class ManifestData(ManifestDataType): """Get a list of all paths containing test items without actually constructing all the items""" rv = set() # type: Set[Text] - for item_data in itervalues(self): + for item_data in self.values(): for item in item_data: rv.add(os.path.sep.join(item)) return rv @@ -117,7 +110,7 @@ class ManifestData(ManifestDataType): def type_by_path(self): # type: () -> Dict[Tuple[Text, ...], Text] rv = {} - for item_type, item_data in iteritems(self): + for item_type, item_data in self.items(): for item in item_data: rv[item] = item_type return rv @@ -159,7 +152,7 @@ class Manifest(object): tpath_len = len(tpath) for type_tests in self._data.values(): - for path, tests in iteritems(type_tests): + for path, tests in type_tests.items(): if path[:tpath_len] == tpath: for test in tests: yield test @@ -253,10 +246,8 @@ class Manifest(object): to_update, chunksize=chunksize ) # type: Iterator[Tuple[Tuple[Text, ...], Text, Set[ManifestItem], Text]] - elif PY3: - results = map(compute_manifest_items, to_update) else: - results = itertools.imap(compute_manifest_items, to_update) + results = map(compute_manifest_items, to_update) for result in results: rel_path_parts, new_type, manifest_items, file_hash = result @@ -271,7 +262,7 @@ class Manifest(object): if remaining_manifest_paths: changed = True for rel_path_parts in remaining_manifest_paths: - for test_data in itervalues(data): + for test_data in data.values(): if rel_path_parts in test_data: del test_data[rel_path_parts] @@ -291,7 +282,7 @@ class Manifest(object): """ out_items = { test_type: type_paths.to_json() - for test_type, type_paths in iteritems(self._data) if type_paths + for test_type, type_paths in self._data.items() if type_paths } if caller_owns_obj: @@ -325,7 +316,7 @@ class Manifest(object): if not hasattr(obj, "items"): raise ManifestError - for test_type, type_paths in iteritems(obj["items"]): + for test_type, type_paths in obj["items"].items(): if test_type not in item_classes: raise ManifestError @@ -358,12 +349,12 @@ def _load(logger, # type: Logger allow_cached=True # type: bool ): # type: (...) -> Optional[Manifest] - manifest_path = (manifest if isinstance(manifest, string_types) + manifest_path = (manifest if isinstance(manifest, str) else manifest.name) if allow_cached and manifest_path in __load_cache: return __load_cache[manifest_path] - if isinstance(manifest, string_types): + if isinstance(manifest, str): if os.path.exists(manifest): logger.debug("Opening manifest at %s" % manifest) else: diff --git a/tests/wpt/web-platform-tests/tools/manifest/sourcefile.py b/tests/wpt/web-platform-tests/tools/manifest/sourcefile.py index ce81c625bb8..5b99f90e458 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/sourcefile.py +++ b/tests/wpt/web-platform-tests/tools/manifest/sourcefile.py @@ -3,8 +3,7 @@ import re import os from collections import deque from io import BytesIO -from six import binary_type, iteritems, text_type -from six.moves.urllib.parse import urljoin +from urllib.parse import urljoin from fnmatch import fnmatch MYPY = False @@ -74,7 +73,7 @@ def read_script_metadata(f, regexp): value. """ for line in f: - assert isinstance(line, binary_type), line + assert isinstance(line, bytes), line m = regexp.match(line) if not m: break @@ -97,7 +96,7 @@ def get_any_variants(item): """ Returns a set of variants (strings) defined by the given keyword. """ - assert isinstance(item, text_type), item + assert isinstance(item, str), item variant = _any_variants.get(item, None) if variant is None: @@ -119,7 +118,7 @@ def parse_variants(value): """ Returns a set of variants (strings) defined by a comma-separated value. """ - assert isinstance(value, text_type), value + assert isinstance(value, str), value if value == "": return get_default_any_variants() @@ -138,7 +137,7 @@ def global_suffixes(value): variant is intended to run in a JS shell, for the variants defined by the given comma-separated value. """ - assert isinstance(value, text_type), value + assert isinstance(value, str), value rv = set() @@ -243,7 +242,7 @@ class SourceFile(object): if "__cached_properties__" in rv: cached_properties = rv["__cached_properties__"] - rv = {key:value for key, value in iteritems(rv) if key not in cached_properties} + rv = {key:value for key, value in rv.items() if key not in cached_properties} del rv["__cached_properties__"] return rv @@ -304,7 +303,7 @@ class SourceFile(object): content = f.read() data = b"".join((b"blob ", b"%d" % len(content), b"\0", content)) - self._hash = text_type(hashlib.sha1(data).hexdigest()) + self._hash = str(hashlib.sha1(data).hexdigest()) return self._hash diff --git a/tests/wpt/web-platform-tests/tools/manifest/testpaths.py b/tests/wpt/web-platform-tests/tools/manifest/testpaths.py index 2197792cd64..6902f0c0633 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/testpaths.py +++ b/tests/wpt/web-platform-tests/tools/manifest/testpaths.py @@ -3,8 +3,6 @@ import json import os from collections import defaultdict -from six import iteritems - from .manifest import load_and_update, Manifest from .log import get_logger @@ -102,7 +100,7 @@ def write_output(path_id_map, as_json): if as_json: print(json.dumps(path_id_map)) else: - for path, test_ids in sorted(iteritems(path_id_map)): + for path, test_ids in sorted(path_id_map.items()): print(path) for test_id in sorted(test_ids): print(" " + test_id) diff --git a/tests/wpt/web-platform-tests/tools/manifest/tests/test_manifest.py b/tests/wpt/web-platform-tests/tools/manifest/tests/test_manifest.py index 293ae0ebcc7..654070fe4db 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/tests/test_manifest.py +++ b/tests/wpt/web-platform-tests/tools/manifest/tests/test_manifest.py @@ -1,18 +1,11 @@ import os -import sys import mock import hypothesis as h import hypothesis.strategies as hs -from six import iteritems - -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - -from tools.manifest import manifest, sourcefile, item, utils +from .. import manifest, sourcefile, item, utils MYPY = False if MYPY: @@ -99,11 +92,11 @@ def manifest_tree(draw): reftest_urls = [] output = [] - stack = [((k,), v) for k, v in iteritems(generated_root)] + stack = [((k,), v) for k, v in generated_root.items()] while stack: path, node = stack.pop() if isinstance(node, dict): - stack.extend((path + (k,), v) for k, v in iteritems(node)) + stack.extend((path + (k,), v) for k, v in node.items()) else: rel_path = os.path.sep.join(path) node.rel_path = rel_path diff --git a/tests/wpt/web-platform-tests/tools/manifest/tests/test_sourcefile.py b/tests/wpt/web-platform-tests/tools/manifest/tests/test_sourcefile.py index cc97619bb57..ef754cdb0f5 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/tests/test_sourcefile.py +++ b/tests/wpt/web-platform-tests/tools/manifest/tests/test_sourcefile.py @@ -2,7 +2,7 @@ import os import pytest -from six import BytesIO +from io import BytesIO from ...lint.lint import check_global_metadata from ..sourcefile import SourceFile, read_script_metadata, js_meta_re, python_meta_re diff --git a/tests/wpt/web-platform-tests/tools/manifest/typedata.py b/tests/wpt/web-platform-tests/tools/manifest/typedata.py index 01bb82705f8..174008eae81 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/typedata.py +++ b/tests/wpt/web-platform-tests/tools/manifest/typedata.py @@ -1,5 +1,4 @@ -from six import itervalues, iteritems -from six.moves.collections_abc import MutableMapping +from collections.abc import MutableMapping MYPY = False @@ -181,7 +180,7 @@ class TypeData(TypeDataType): if isinstance(v, set): count += 1 else: - stack.extend(itervalues(v)) + stack.extend(v.values()) stack = [self._json_data] while stack: @@ -189,7 +188,7 @@ class TypeData(TypeDataType): if isinstance(v, list): count += 1 else: - stack.extend(itervalues(v)) + stack.extend(v.values()) return count @@ -270,7 +269,7 @@ class TypeData(TypeDataType): stack = [(self._data, json_rv, tuple())] # type: List[Tuple[Dict[Text, Any], Dict[Text, Any], Tuple[Text, ...]]] while stack: data_node, json_node, par_full_key = stack.pop() - for k, v in iteritems(data_node): + for k, v in data_node.items(): full_key = par_full_key + (k,) if isinstance(v, set): assert k not in json_node diff --git a/tests/wpt/web-platform-tests/tools/manifest/vcs.py b/tests/wpt/web-platform-tests/tools/manifest/vcs.py index 4f3bfdaf124..65ba308ea24 100644 --- a/tests/wpt/web-platform-tests/tools/manifest/vcs.py +++ b/tests/wpt/web-platform-tests/tools/manifest/vcs.py @@ -2,9 +2,7 @@ import abc import os import stat from collections import deque - -from six import with_metaclass, PY2 -from six.moves.collections_abc import MutableMapping +from collections.abc import MutableMapping from . import jsonlib from .utils import git @@ -19,10 +17,7 @@ if MYPY: # MYPY is set to True when run under Mypy. from typing import Dict, Optional, List, Set, Text, Iterable, Any, Tuple, Iterator from .manifest import Manifest # cyclic import under MYPY guard - if PY2: - stat_result = Any - else: - stat_result = os.stat_result + stat_result = os.stat_result GitIgnoreCacheType = MutableMapping[bytes, bool] else: @@ -132,7 +127,7 @@ class FileSystem(object): cache.dump() -class CacheFile(with_metaclass(abc.ABCMeta)): +class CacheFile(metaclass=abc.ABCMeta): def __init__(self, cache_root, tests_root, rebuild=False): # type: (Text, Text, bool) -> None self.tests_root = tests_root diff --git a/tests/wpt/web-platform-tests/tools/quic/commands.json b/tests/wpt/web-platform-tests/tools/quic/commands.json index d4e3ce81280..4496f4836ff 100644 --- a/tests/wpt/web-platform-tests/tools/quic/commands.json +++ b/tests/wpt/web-platform-tests/tools/quic/commands.json @@ -3,7 +3,6 @@ "path": "serve.py", "script": "run", "parser": "get_parser", - "py3only": true, "help": "Start the QUIC server for WebTransport", "virtualenv": true, "requirements": [ diff --git a/tests/wpt/web-platform-tests/tools/runner/report.py b/tests/wpt/web-platform-tests/tools/runner/report.py index e9986fdb398..c22994ced00 100644 --- a/tests/wpt/web-platform-tests/tools/runner/report.py +++ b/tests/wpt/web-platform-tests/tools/runner/report.py @@ -9,7 +9,6 @@ import types from cgi import escape from collections import defaultdict -from six import iteritems def html_escape(item, escape_quote=False): @@ -45,7 +44,7 @@ class Node(object): attrs_unicode = " " + " ".join("%s=\"%s\"" % (html_escape(key), html_escape(value, escape_quote=True)) - for key, value in iteritems(self.attrs)) + for key, value in self.attrs.items()) else: attrs_unicode = "" return "<%s%s>%s</%s>\n" % (self.name, diff --git a/tests/wpt/web-platform-tests/tools/serve/serve.py b/tests/wpt/web-platform-tests/tools/serve/serve.py index f09fe242237..644a8a93f70 100644 --- a/tests/wpt/web-platform-tests/tools/serve/serve.py +++ b/tests/wpt/web-platform-tests/tools/serve/serve.py @@ -4,6 +4,7 @@ from __future__ import print_function import abc import argparse +import importlib import json import logging import multiprocessing @@ -16,13 +17,12 @@ import sys import threading import time import traceback -from six.moves import urllib +import urllib import uuid from collections import defaultdict, OrderedDict from itertools import chain, product from localpaths import repo_root -from six.moves import reload_module from manifest.sourcefile import read_script_metadata, js_meta_re, parse_variants from wptserve import server as wptserve, handlers @@ -697,7 +697,7 @@ def release_mozlog_lock(): def start_ws_server(host, port, paths, routes, bind_address, config, **kwargs): # Ensure that when we start this in a new process we have the global lock # in the logging module unlocked - reload_module(logging) + importlib.reload(logging) release_mozlog_lock() try: return WebSocketDaemon(host, @@ -713,7 +713,7 @@ def start_ws_server(host, port, paths, routes, bind_address, config, **kwargs): def start_wss_server(host, port, paths, routes, bind_address, config, **kwargs): # Ensure that when we start this in a new process we have the global lock # in the logging module unlocked - reload_module(logging) + importlib.reload(logging) release_mozlog_lock() try: return WebSocketDaemon(host, @@ -771,7 +771,7 @@ class QuicTransportDaemon(object): def start_quic_transport_server(host, port, paths, routes, bind_address, config, **kwargs): # Ensure that when we start this in a new process we have the global lock # in the logging module unlocked - reload_module(logging) + importlib.reload(logging) release_mozlog_lock() try: return QuicTransportDaemon(host, diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.gitignore b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.gitignore new file mode 100644 index 00000000000..89e6234380c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.gitignore @@ -0,0 +1,8 @@ +*.egg-info +*.pyc +.cache/ +.eggs/ +build/ +dist/ +__pycache__ +.tox/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.hgignore b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.hgignore new file mode 100644 index 00000000000..e8e91abab1e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.hgignore @@ -0,0 +1,16 @@ + +# These lines are suggested according to the svn:ignore property +# Feel free to enable them by uncommenting them +syntax:glob +*.pyc +*.pyo +*.swp +*.html +*.class + +.tox + +build +dist +*.egg-info + diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.landscape.yml b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.landscape.yml new file mode 100644 index 00000000000..5212ddea412 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.landscape.yml @@ -0,0 +1,5 @@ +pep8: + full: true +python-targets: + - 2 + - 3 diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.travis.yml b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.travis.yml new file mode 100644 index 00000000000..e3fee06c9fa --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/.travis.yml @@ -0,0 +1,18 @@ +language: python +python: +- '2.7' +- '3.4' +- '3.5' +- nightly +- pypy +install: pip install setuptools_scm tox +script: tox -e py +deploy: + provider: pypi + user: ronny + password: + secure: DsRVX99HA6+3JoXOVP/nPXeabJy2P73ws7Ager/e4rx3p3jS74bId09XsBU46bAT9ANmRWPR8y5DRi5Zlq0WQ2uXoR55wmsdu2KUegk6bDIS4Iop8DFxY8Kjou9s8RZbDTP27LfuYXKMO1rDW/xa6EhiotYRodekeZUz3P3MYjIi6rBV2Rz3vwmInpkKOti7AFwAsCGmCCK13irmPJEp5nwl3RgeKu2AGaolw9eypJXeNLUcNDVQ88ZUUXQCkwgq7a1BkK6NMeQLMrWAE1bD3amCbVXHCR9TaVx1ZH1dnha5Jcfj3gEFucTmInWWes5u9rypvsCkSxKtSqdiUA7BMJq7XykV7nGNplGLm2sq4+KSYlf3gZXg4XNXQkNOi4EBtRvathfFziD2SZgdtjiQX2neh0dMjf9czc/uCYkKYCFLeozdw2oQQ+BsxhQfsmU2ILGCFHyFikmDbBqZOWfQE5TN3itQqV3TFK8sOHQ8iy3MDShs+lBk9AUwbCA5YbRh8hJKhgXyEsDpisC417Pj22+TbutTj7v3Rmpe/st4hoL740grWc3PSVUBaypG0RsoafSDZWnYnTC+0aakd6QEb5S9wnMkP94kijYjjF6yUInuT05wdbQv5XcSXqAdGzBqB5jNNdfwgWVCOlwGfjnvzKllhF3PmWPW/nfmQpGOQh4= + on: + tags: true + distributions: sdist bdist_wheel + repo: RonnyPfannschmidt/iniconfig diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/CHANGELOG b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/CHANGELOG new file mode 100644 index 00000000000..679919fcd2e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/CHANGELOG @@ -0,0 +1,32 @@ +1.1.1 +========= + +* fix version determination (thanks @florimondmanca) + +1.1.0 +===== + +- typing stubs (thanks @bluetech) +- ci fixes + +1.0.1 +====== + +pytest 5+ support + +1.0 +==== + +- re-sync with pylib codebase + +0.2 +================== + +- added ability to ask "name in iniconfig", i.e. to check + if a section is contained. + +- fix bug in "name=value" parsing where value was "x=3" + +- allow for ': ' to delimit name=value pairs, so that e.g. .pypirc files + like http://docs.python.org/distutils/packageindex.html + can be successfully parsed diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/LICENSE b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/LICENSE new file mode 100644 index 00000000000..31ecdfb1dbc --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/LICENSE @@ -0,0 +1,19 @@ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/MANIFEST.in b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/MANIFEST.in new file mode 100644 index 00000000000..06be514ae52 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/MANIFEST.in @@ -0,0 +1,5 @@ +include LICENSE +include example.ini +include tox.ini +include src/iniconfig/py.typed +recursive-include src *.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/README.txt b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/README.txt new file mode 100644 index 00000000000..6bbad9a8d96 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/README.txt @@ -0,0 +1,51 @@ +iniconfig: brain-dead simple parsing of ini files +======================================================= + +iniconfig is a small and simple INI-file parser module +having a unique set of features: + +* tested against Python2.4 across to Python3.2, Jython, PyPy +* maintains order of sections and entries +* supports multi-line values with or without line-continuations +* supports "#" comments everywhere +* raises errors with proper line-numbers +* no bells and whistles like automatic substitutions +* iniconfig raises an Error if two sections have the same name. + +If you encounter issues or have feature wishes please report them to: + + http://github.com/RonnyPfannschmidt/iniconfig/issues + +Basic Example +=================================== + +If you have an ini file like this:: + + # content of example.ini + [section1] # comment + name1=value1 # comment + name1b=value1,value2 # comment + + [section2] + name2= + line1 + line2 + +then you can do:: + + >>> import iniconfig + >>> ini = iniconfig.IniConfig("example.ini") + >>> ini['section1']['name1'] # raises KeyError if not exists + 'value1' + >>> ini.get('section1', 'name1b', [], lambda x: x.split(",")) + ['value1', 'value2'] + >>> ini.get('section1', 'notexist', [], lambda x: x.split(",")) + [] + >>> [x.name for x in list(ini)] + ['section1', 'section2'] + >>> list(list(ini)[0].items()) + [('name1', 'value1'), ('name1b', 'value1,value2')] + >>> 'section1' in ini + True + >>> 'inexistendsection' in ini + False diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/example.ini b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/example.ini new file mode 100644 index 00000000000..65481d2074a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/example.ini @@ -0,0 +1,10 @@ + +# content of example.ini +[section1] # comment +name1=value1 # comment +name1b=value1,value2 # comment + +[section2] +name2= + line1 + line2 diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/pyproject.toml b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/pyproject.toml new file mode 100644 index 00000000000..b2725d8f65f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/pyproject.toml @@ -0,0 +1,5 @@ +[build-system] +requires = ["setuptools>=41.2.0", "wheel", "setuptools_scm>3"] + + +[tool.setuptools_scm]
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/setup.cfg b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/setup.cfg new file mode 100644 index 00000000000..3c6e79cf31d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/setup.py b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/setup.py new file mode 100644 index 00000000000..f46f3214de9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/setup.py @@ -0,0 +1,46 @@ +""" +iniconfig: brain-dead simple config-ini parsing. + +compatible CPython 2.3 through to CPython 3.2, Jython, PyPy + +(c) 2010 Ronny Pfannschmidt, Holger Krekel +""" + +from setuptools import setup + + +def main(): + with open('README.txt') as fp: + readme = fp.read() + setup( + name='iniconfig', + packages=['iniconfig'], + package_dir={'': 'src'}, + description='iniconfig: brain-dead simple config-ini parsing', + long_description=readme, + use_scm_version=True, + url='http://github.com/RonnyPfannschmidt/iniconfig', + license='MIT License', + platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], + author='Ronny Pfannschmidt, Holger Krekel', + author_email=( + 'opensource@ronnypfannschmidt.de, holger.krekel@gmail.com'), + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: POSIX', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS :: MacOS X', + 'Topic :: Software Development :: Libraries', + 'Topic :: Utilities', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', + ], + include_package_data=True, + zip_safe=False, + ) + +if __name__ == '__main__': + main() diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.py new file mode 100644 index 00000000000..6ad9eaf868b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.py @@ -0,0 +1,165 @@ +""" brain-dead simple parser for ini-style files. +(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed +""" +__all__ = ['IniConfig', 'ParseError'] + +COMMENTCHARS = "#;" + + +class ParseError(Exception): + def __init__(self, path, lineno, msg): + Exception.__init__(self, path, lineno, msg) + self.path = path + self.lineno = lineno + self.msg = msg + + def __str__(self): + return "%s:%s: %s" % (self.path, self.lineno+1, self.msg) + + +class SectionWrapper(object): + def __init__(self, config, name): + self.config = config + self.name = name + + def lineof(self, name): + return self.config.lineof(self.name, name) + + def get(self, key, default=None, convert=str): + return self.config.get(self.name, key, + convert=convert, default=default) + + def __getitem__(self, key): + return self.config.sections[self.name][key] + + def __iter__(self): + section = self.config.sections.get(self.name, []) + + def lineof(key): + return self.config.lineof(self.name, key) + for name in sorted(section, key=lineof): + yield name + + def items(self): + for name in self: + yield name, self[name] + + +class IniConfig(object): + def __init__(self, path, data=None): + self.path = str(path) # convenience + if data is None: + f = open(self.path) + try: + tokens = self._parse(iter(f)) + finally: + f.close() + else: + tokens = self._parse(data.splitlines(True)) + + self._sources = {} + self.sections = {} + + for lineno, section, name, value in tokens: + if section is None: + self._raise(lineno, 'no section header defined') + self._sources[section, name] = lineno + if name is None: + if section in self.sections: + self._raise(lineno, 'duplicate section %r' % (section, )) + self.sections[section] = {} + else: + if name in self.sections[section]: + self._raise(lineno, 'duplicate name %r' % (name, )) + self.sections[section][name] = value + + def _raise(self, lineno, msg): + raise ParseError(self.path, lineno, msg) + + def _parse(self, line_iter): + result = [] + section = None + for lineno, line in enumerate(line_iter): + name, data = self._parseline(line, lineno) + # new value + if name is not None and data is not None: + result.append((lineno, section, name, data)) + # new section + elif name is not None and data is None: + if not name: + self._raise(lineno, 'empty section name') + section = name + result.append((lineno, section, None, None)) + # continuation + elif name is None and data is not None: + if not result: + self._raise(lineno, 'unexpected value continuation') + last = result.pop() + last_name, last_data = last[-2:] + if last_name is None: + self._raise(lineno, 'unexpected value continuation') + + if last_data: + data = '%s\n%s' % (last_data, data) + result.append(last[:-1] + (data,)) + return result + + def _parseline(self, line, lineno): + # blank lines + if iscommentline(line): + line = "" + else: + line = line.rstrip() + if not line: + return None, None + # section + if line[0] == '[': + realline = line + for c in COMMENTCHARS: + line = line.split(c)[0].rstrip() + if line[-1] == "]": + return line[1:-1], None + return None, realline.strip() + # value + elif not line[0].isspace(): + try: + name, value = line.split('=', 1) + if ":" in name: + raise ValueError() + except ValueError: + try: + name, value = line.split(":", 1) + except ValueError: + self._raise(lineno, 'unexpected line: %r' % line) + return name.strip(), value.strip() + # continuation + else: + return None, line.strip() + + def lineof(self, section, name=None): + lineno = self._sources.get((section, name)) + if lineno is not None: + return lineno + 1 + + def get(self, section, name, default=None, convert=str): + try: + return convert(self.sections[section][name]) + except KeyError: + return default + + def __getitem__(self, name): + if name not in self.sections: + raise KeyError(name) + return SectionWrapper(self, name) + + def __iter__(self): + for name in sorted(self.sections, key=self.lineof): + yield SectionWrapper(self, name) + + def __contains__(self, arg): + return arg in self.sections + + +def iscommentline(line): + c = line.lstrip()[:1] + return c in COMMENTCHARS diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.pyi b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.pyi new file mode 100644 index 00000000000..b6284bec3f6 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/__init__.pyi @@ -0,0 +1,31 @@ +from typing import Callable, Iterator, Mapping, Optional, Tuple, TypeVar, Union +from typing_extensions import Final + +_D = TypeVar('_D') +_T = TypeVar('_T') + +class ParseError(Exception): + # Private __init__. + path: Final[str] + lineno: Final[int] + msg: Final[str] + +class SectionWrapper: + # Private __init__. + config: Final[IniConfig] + name: Final[str] + def __getitem__(self, key: str) -> str: ... + def __iter__(self) -> Iterator[str]: ... + def get(self, key: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ... + def items(self) -> Iterator[Tuple[str, str]]: ... + def lineof(self, name: str) -> Optional[int]: ... + +class IniConfig: + path: Final[str] + sections: Final[Mapping[str, Mapping[str, str]]] + def __init__(self, path: str, data: Optional[str] = None): ... + def __contains__(self, arg: str) -> bool: ... + def __getitem__(self, name: str) -> SectionWrapper: ... + def __iter__(self) -> Iterator[SectionWrapper]: ... + def get(self, section: str, name: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ... + def lineof(self, section: str, name: Optional[str] = ...) -> Optional[int]: ... diff --git a/tests/wpt/web-platform-tests/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/py.typed index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/src/iniconfig/py.typed diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/testing/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/testing/conftest.py new file mode 100644 index 00000000000..d265a29f865 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/testing/conftest.py @@ -0,0 +1,2 @@ + +option_doctestglob = "README.txt" diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/testing/test_iniconfig.py b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/testing/test_iniconfig.py new file mode 100644 index 00000000000..fe12421e5a8 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/testing/test_iniconfig.py @@ -0,0 +1,314 @@ +import py +import pytest +from iniconfig import IniConfig, ParseError, __all__ as ALL +from iniconfig import iscommentline +from textwrap import dedent + + +check_tokens = { + 'section': ( + '[section]', + [(0, 'section', None, None)] + ), + 'value': ( + 'value = 1', + [(0, None, 'value', '1')] + ), + 'value in section': ( + '[section]\nvalue=1', + [(0, 'section', None, None), (1, 'section', 'value', '1')] + ), + 'value with continuation': ( + 'names =\n Alice\n Bob', + [(0, None, 'names', 'Alice\nBob')] + ), + 'value with aligned continuation': ( + 'names = Alice\n' + ' Bob', + [(0, None, 'names', 'Alice\nBob')] + ), + 'blank line': ( + '[section]\n\nvalue=1', + [(0, 'section', None, None), (2, 'section', 'value', '1')] + ), + 'comment': ( + '# comment', + [] + ), + 'comment on value': ( + 'value = 1', + [(0, None, 'value', '1')] + ), + + 'comment on section': ( + '[section] #comment', + [(0, 'section', None, None)] + ), + 'comment2': ( + '; comment', + [] + ), + + 'comment2 on section': ( + '[section] ;comment', + [(0, 'section', None, None)] + ), + 'pseudo section syntax in value': ( + 'name = value []', + [(0, None, 'name', 'value []')] + ), + 'assignment in value': ( + 'value = x = 3', + [(0, None, 'value', 'x = 3')] + ), + 'use of colon for name-values': ( + 'name: y', + [(0, None, 'name', 'y')] + ), + 'use of colon without space': ( + 'value:y=5', + [(0, None, 'value', 'y=5')] + ), + 'equality gets precedence': ( + 'value=xyz:5', + [(0, None, 'value', 'xyz:5')] + ), + +} + + +@pytest.fixture(params=sorted(check_tokens)) +def input_expected(request): + return check_tokens[request.param] + + +@pytest.fixture +def input(input_expected): + return input_expected[0] + + +@pytest.fixture +def expected(input_expected): + return input_expected[1] + + +def parse(input): + # only for testing purposes - _parse() does not use state except path + ini = object.__new__(IniConfig) + ini.path = "sample" + return ini._parse(input.splitlines(True)) + + +def parse_a_error(input): + return py.test.raises(ParseError, parse, input) + + +def test_tokenize(input, expected): + parsed = parse(input) + assert parsed == expected + + +def test_parse_empty(): + parsed = parse("") + assert not parsed + ini = IniConfig("sample", "") + assert not ini.sections + + +def test_ParseError(): + e = ParseError("filename", 0, "hello") + assert str(e) == "filename:1: hello" + + +def test_continuation_needs_perceeding_token(): + excinfo = parse_a_error(' Foo') + assert excinfo.value.lineno == 0 + + +def test_continuation_cant_be_after_section(): + excinfo = parse_a_error('[section]\n Foo') + assert excinfo.value.lineno == 1 + + +def test_section_cant_be_empty(): + excinfo = parse_a_error('[]') + assert excinfo.value.lineno == 0 + + +@py.test.mark.parametrize('line', [ + '!!', + ]) +def test_error_on_weird_lines(line): + parse_a_error(line) + + +def test_iniconfig_from_file(tmpdir): + path = tmpdir/'test.txt' + path.write('[metadata]\nname=1') + + config = IniConfig(path=path) + assert list(config.sections) == ['metadata'] + config = IniConfig(path, "[diff]") + assert list(config.sections) == ['diff'] + with pytest.raises(TypeError): + IniConfig(data=path.read()) + + +def test_iniconfig_section_first(tmpdir): + with pytest.raises(ParseError) as excinfo: + IniConfig("x", data='name=1') + assert excinfo.value.msg == "no section header defined" + + +def test_iniconig_section_duplicate_fails(): + with pytest.raises(ParseError) as excinfo: + IniConfig("x", data='[section]\n[section]') + assert 'duplicate section' in str(excinfo.value) + + +def test_iniconfig_duplicate_key_fails(): + with pytest.raises(ParseError) as excinfo: + IniConfig("x", data='[section]\nname = Alice\nname = bob') + + assert 'duplicate name' in str(excinfo.value) + + +def test_iniconfig_lineof(): + config = IniConfig("x.ini", data=( + '[section]\n' + 'value = 1\n' + '[section2]\n' + '# comment\n' + 'value =2' + )) + + assert config.lineof('missing') is None + assert config.lineof('section') == 1 + assert config.lineof('section2') == 3 + assert config.lineof('section', 'value') == 2 + assert config.lineof('section2', 'value') == 5 + + assert config['section'].lineof('value') == 2 + assert config['section2'].lineof('value') == 5 + + +def test_iniconfig_get_convert(): + config = IniConfig("x", data='[section]\nint = 1\nfloat = 1.1') + assert config.get('section', 'int') == '1' + assert config.get('section', 'int', convert=int) == 1 + + +def test_iniconfig_get_missing(): + config = IniConfig("x", data='[section]\nint = 1\nfloat = 1.1') + assert config.get('section', 'missing', default=1) == 1 + assert config.get('section', 'missing') is None + + +def test_section_get(): + config = IniConfig("x", data='[section]\nvalue=1') + section = config['section'] + assert section.get('value', convert=int) == 1 + assert section.get('value', 1) == "1" + assert section.get('missing', 2) == 2 + + +def test_missing_section(): + config = IniConfig("x", data='[section]\nvalue=1') + with pytest.raises(KeyError): + config["other"] + + +def test_section_getitem(): + config = IniConfig("x", data='[section]\nvalue=1') + assert config['section']['value'] == '1' + assert config['section']['value'] == '1' + + +def test_section_iter(): + config = IniConfig("x", data='[section]\nvalue=1') + names = list(config['section']) + assert names == ['value'] + items = list(config['section'].items()) + assert items == [('value', '1')] + + +def test_config_iter(): + config = IniConfig("x.ini", data=dedent(''' + [section1] + value=1 + [section2] + value=2 + ''')) + l = list(config) + assert len(l) == 2 + assert l[0].name == 'section1' + assert l[0]['value'] == '1' + assert l[1].name == 'section2' + assert l[1]['value'] == '2' + + +def test_config_contains(): + config = IniConfig("x.ini", data=dedent(''' + [section1] + value=1 + [section2] + value=2 + ''')) + assert 'xyz' not in config + assert 'section1' in config + assert 'section2' in config + + +def test_iter_file_order(): + config = IniConfig("x.ini", data=""" +[section2] #cpython dict ordered before section +value = 1 +value2 = 2 # dict ordered before value +[section] +a = 1 +b = 2 +""") + l = list(config) + secnames = [x.name for x in l] + assert secnames == ['section2', 'section'] + assert list(config['section2']) == ['value', 'value2'] + assert list(config['section']) == ['a', 'b'] + + +def test_example_pypirc(): + config = IniConfig("pypirc", data=dedent(''' + [distutils] + index-servers = + pypi + other + + [pypi] + repository: <repository-url> + username: <username> + password: <password> + + [other] + repository: http://example.com/pypi + username: <username> + password: <password> + ''')) + distutils, pypi, other = list(config) + assert distutils["index-servers"] == "pypi\nother" + assert pypi['repository'] == '<repository-url>' + assert pypi['username'] == '<username>' + assert pypi['password'] == '<password>' + assert ['repository', 'username', 'password'] == list(other) + + +def test_api_import(): + assert ALL == ['IniConfig', 'ParseError'] + + +@pytest.mark.parametrize("line", [ + "#qwe", + " #qwe", + ";qwe", + " ;qwe", +]) +def test_iscommentline_true(line): + assert iscommentline(line) diff --git a/tests/wpt/web-platform-tests/tools/third_party/iniconfig/tox.ini b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/tox.ini new file mode 100644 index 00000000000..298838bee07 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/iniconfig/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist=py27,py26,py33,py34,py35 + + +[testenv] +commands= + pytest {posargs} +deps= + pytest + + +[pytest] +testpaths= + testing diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/LICENSE b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/LICENSE new file mode 100644 index 00000000000..e06d2081865 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/PKG-INFO b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/PKG-INFO new file mode 100644 index 00000000000..bb7611bc1a7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/PKG-INFO @@ -0,0 +1,302 @@ +Metadata-Version: 2.1 +Name: pytest-asyncio +Version: 0.14.0 +Summary: Pytest support for asyncio. +Home-page: https://github.com/pytest-dev/pytest-asyncio +Author: Tin Tvrtković +Author-email: tinchester@gmail.com +License: Apache 2.0 +Description: pytest-asyncio: pytest support for asyncio + ========================================== + + .. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg + :target: https://pypi.python.org/pypi/pytest-asyncio + .. image:: https://travis-ci.org/pytest-dev/pytest-asyncio.svg?branch=master + :target: https://travis-ci.org/pytest-dev/pytest-asyncio + .. image:: https://coveralls.io/repos/pytest-dev/pytest-asyncio/badge.svg + :target: https://coveralls.io/r/pytest-dev/pytest-asyncio + .. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg + :target: https://github.com/pytest-dev/pytest-asyncio + :alt: Supported Python versions + + pytest-asyncio is an Apache2 licensed library, written in Python, for testing + asyncio code with pytest. + + asyncio code is usually written in the form of coroutines, which makes it + slightly more difficult to test using normal testing tools. pytest-asyncio + provides useful fixtures and markers to make testing easier. + + .. code-block:: python + + @pytest.mark.asyncio + async def test_some_asyncio_code(): + res = await library.do_something() + assert b'expected result' == res + + pytest-asyncio has been strongly influenced by pytest-tornado_. + + .. _pytest-tornado: https://github.com/eugeniy/pytest-tornado + + Features + -------- + + - fixtures for creating and injecting versions of the asyncio event loop + - fixtures for injecting unused tcp ports + - pytest markers for treating tests as asyncio coroutines + - easy testing with non-default event loops + - support for `async def` fixtures and async generator fixtures + + Installation + ------------ + + To install pytest-asyncio, simply: + + .. code-block:: bash + + $ pip install pytest-asyncio + + This is enough for pytest to pick up pytest-asyncio. + + Fixtures + -------- + + ``event_loop`` + ~~~~~~~~~~~~~~ + Creates and injects a new instance of the default asyncio event loop. By + default, the loop will be closed at the end of the test (i.e. the default + fixture scope is ``function``). + + Note that just using the ``event_loop`` fixture won't make your test function + a coroutine. You'll need to interact with the event loop directly, using methods + like ``event_loop.run_until_complete``. See the ``pytest.mark.asyncio`` marker + for treating test functions like coroutines. + + Simply using this fixture will not set the generated event loop as the + default asyncio event loop, or change the asyncio event loop policy in any way. + Use ``pytest.mark.asyncio`` for this purpose. + + .. code-block:: python + + def test_http_client(event_loop): + url = 'http://httpbin.org/get' + resp = event_loop.run_until_complete(http_client(url)) + assert b'HTTP/1.1 200 OK' in resp + + This fixture can be easily overridden in any of the standard pytest locations + (e.g. directly in the test file, or in ``conftest.py``) to use a non-default + event loop. This will take effect even if you're using the + ``pytest.mark.asyncio`` marker and not the ``event_loop`` fixture directly. + + .. code-block:: python + + @pytest.fixture + def event_loop(): + loop = MyCustomLoop() + yield loop + loop.close() + + If the ``pytest.mark.asyncio`` marker is applied, a pytest hook will + ensure the produced loop is set as the default global loop. + Fixtures depending on the ``event_loop`` fixture can expect the policy to be properly modified when they run. + + ``unused_tcp_port`` + ~~~~~~~~~~~~~~~~~~~ + Finds and yields a single unused TCP port on the localhost interface. Useful for + binding temporary test servers. + + ``unused_tcp_port_factory`` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A callable which returns a different unused TCP port each invocation. Useful + when several unused TCP ports are required in a test. + + .. code-block:: python + + def a_test(unused_tcp_port_factory): + port1, port2 = unused_tcp_port_factory(), unused_tcp_port_factory() + ... + + Async fixtures + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be coroutines or asynchronous generators. + + .. code-block:: python3 + + @pytest.fixture + async def async_gen_fixture(): + await asyncio.sleep(0.1) + yield 'a value' + + @pytest.fixture(scope='module') + async def async_fixture(): + return await asyncio.sleep(0.1) + + All scopes are supported, but if you use a non-function scope you will need + to redefine the ``event_loop`` fixture to have the same or broader scope. + Async fixtures need the event loop, and so must have the same or narrower scope + than the ``event_loop`` fixture. + + If you want to do this with Python 3.5, the ``yield`` statement must be replaced with ``await yield_()`` and the coroutine + function must be decorated with ``@async_generator``, like so: + + .. code-block:: python3 + + from async_generator import yield_, async_generator + + @pytest.fixture + @async_generator + async def async_gen_fixture(): + await asyncio.sleep(0.1) + await yield_('a value') + + + Markers + ------- + + ``pytest.mark.asyncio`` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Mark your test coroutine with this marker and pytest will execute it as an + asyncio task using the event loop provided by the ``event_loop`` fixture. See + the introductory section for an example. + + The event loop used can be overriden by overriding the ``event_loop`` fixture + (see above). + + In order to make your test code a little more concise, the pytest |pytestmark|_ + feature can be used to mark entire modules or classes with this marker. + Only test coroutines will be affected (by default, coroutines prefixed by + ``test_``), so, for example, fixtures are safe to define. + + .. code-block:: python + + import asyncio + import pytest + + # All test coroutines will be treated as marked. + pytestmark = pytest.mark.asyncio + + async def test_example(event_loop): + """No marker!""" + await asyncio.sleep(0, loop=event_loop) + + .. |pytestmark| replace:: ``pytestmark`` + .. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules + + Changelog + --------- + 0.13.0 (2020-06-24) + ~~~~~~~~~~~~~~~~~~~ + - Fix `#162 <https://github.com/pytest-dev/pytest-asyncio/issues/162>`_, and ``event_loop`` fixture behavior now is coherent on all scopes. + `#164 <https://github.com/pytest-dev/pytest-asyncio/pull/164>`_ + + 0.12.0 (2020-05-04) + ~~~~~~~~~~~~~~~~~~~ + - Run the event loop fixture as soon as possible. This helps with fixtures that have an implicit dependency on the event loop. + `#156 <https://github.com/pytest-dev/pytest-asyncio/pull/156>`_ + + 0.11.0 (2020-04-20) + ~~~~~~~~~~~~~~~~~~~ + - Test on 3.8, drop 3.3 and 3.4. Stick to 0.10 for these versions. + `#152 <https://github.com/pytest-dev/pytest-asyncio/pull/152>`_ + - Use the new Pytest 5.4.0 Function API. We therefore depend on pytest >= 5.4.0. + `#142 <https://github.com/pytest-dev/pytest-asyncio/pull/142>`_ + - Better ``pytest.skip`` support. + `#126 <https://github.com/pytest-dev/pytest-asyncio/pull/126>`_ + + 0.10.0 (2019-01-08) + ~~~~~~~~~~~~~~~~~~~~ + - ``pytest-asyncio`` integrates with `Hypothesis <https://hypothesis.readthedocs.io>`_ + to support ``@given`` on async test functions using ``asyncio``. + `#102 <https://github.com/pytest-dev/pytest-asyncio/pull/102>`_ + - Pytest 4.1 support. + `#105 <https://github.com/pytest-dev/pytest-asyncio/pull/105>`_ + + 0.9.0 (2018-07-28) + ~~~~~~~~~~~~~~~~~~ + - Python 3.7 support. + - Remove ``event_loop_process_pool`` fixture and + ``pytest.mark.asyncio_process_pool`` marker (see + https://bugs.python.org/issue34075 for deprecation and removal details) + + 0.8.0 (2017-09-23) + ~~~~~~~~~~~~~~~~~~ + - Improve integration with other packages (like aiohttp) with more careful event loop handling. + `#64 <https://github.com/pytest-dev/pytest-asyncio/pull/64>`_ + + 0.7.0 (2017-09-08) + ~~~~~~~~~~~~~~~~~~ + - Python versions pre-3.6 can use the async_generator library for async fixtures. + `#62 <https://github.com/pytest-dev/pytest-asyncio/pull/62>` + + + 0.6.0 (2017-05-28) + ~~~~~~~~~~~~~~~~~~ + - Support for Python versions pre-3.5 has been dropped. + - ``pytestmark`` now works on both module and class level. + - The ``forbid_global_loop`` parameter has been removed. + - Support for async and async gen fixtures has been added. + `#45 <https://github.com/pytest-dev/pytest-asyncio/pull/45>`_ + - The deprecation warning regarding ``asyncio.async()`` has been fixed. + `#51 <https://github.com/pytest-dev/pytest-asyncio/pull/51>`_ + + 0.5.0 (2016-09-07) + ~~~~~~~~~~~~~~~~~~ + - Introduced a changelog. + `#31 <https://github.com/pytest-dev/pytest-asyncio/issues/31>`_ + - The ``event_loop`` fixture is again responsible for closing itself. + This makes the fixture slightly harder to correctly override, but enables + other fixtures to depend on it correctly. + `#30 <https://github.com/pytest-dev/pytest-asyncio/issues/30>`_ + - Deal with the event loop policy by wrapping a special pytest hook, + ``pytest_fixture_setup``. This allows setting the policy before fixtures + dependent on the ``event_loop`` fixture run, thus allowing them to take + advantage of the ``forbid_global_loop`` parameter. As a consequence of this, + we now depend on pytest 3.0. + `#29 <https://github.com/pytest-dev/pytest-asyncio/issues/29>`_ + + + 0.4.1 (2016-06-01) + ~~~~~~~~~~~~~~~~~~ + - Fix a bug preventing the propagation of exceptions from the plugin. + `#25 <https://github.com/pytest-dev/pytest-asyncio/issues/25>`_ + + 0.4.0 (2016-05-30) + ~~~~~~~~~~~~~~~~~~ + - Make ``event_loop`` fixtures simpler to override by closing them in the + plugin, instead of directly in the fixture. + `#21 <https://github.com/pytest-dev/pytest-asyncio/pull/21>`_ + - Introduce the ``forbid_global_loop`` parameter. + `#21 <https://github.com/pytest-dev/pytest-asyncio/pull/21>`_ + + 0.3.0 (2015-12-19) + ~~~~~~~~~~~~~~~~~~ + - Support for Python 3.5 ``async``/``await`` syntax. + `#17 <https://github.com/pytest-dev/pytest-asyncio/pull/17>`_ + + 0.2.0 (2015-08-01) + ~~~~~~~~~~~~~~~~~~ + - ``unused_tcp_port_factory`` fixture. + `#10 <https://github.com/pytest-dev/pytest-asyncio/issues/10>`_ + + + 0.1.1 (2015-04-23) + ~~~~~~~~~~~~~~~~~~ + Initial release. + + + Contributing + ------------ + Contributions are very welcome. Tests can be run with ``tox``, please ensure + the coverage at least stays the same before you submit a pull request. + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development :: Testing +Classifier: Framework :: Pytest +Requires-Python: >= 3.5 +Provides-Extra: testing diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/README.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/README.rst new file mode 100644 index 00000000000..6ea6014cce4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/README.rst @@ -0,0 +1,281 @@ +pytest-asyncio: pytest support for asyncio +========================================== + +.. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg + :target: https://pypi.python.org/pypi/pytest-asyncio +.. image:: https://travis-ci.org/pytest-dev/pytest-asyncio.svg?branch=master + :target: https://travis-ci.org/pytest-dev/pytest-asyncio +.. image:: https://coveralls.io/repos/pytest-dev/pytest-asyncio/badge.svg + :target: https://coveralls.io/r/pytest-dev/pytest-asyncio +.. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg + :target: https://github.com/pytest-dev/pytest-asyncio + :alt: Supported Python versions + +pytest-asyncio is an Apache2 licensed library, written in Python, for testing +asyncio code with pytest. + +asyncio code is usually written in the form of coroutines, which makes it +slightly more difficult to test using normal testing tools. pytest-asyncio +provides useful fixtures and markers to make testing easier. + +.. code-block:: python + + @pytest.mark.asyncio + async def test_some_asyncio_code(): + res = await library.do_something() + assert b'expected result' == res + +pytest-asyncio has been strongly influenced by pytest-tornado_. + +.. _pytest-tornado: https://github.com/eugeniy/pytest-tornado + +Features +-------- + +- fixtures for creating and injecting versions of the asyncio event loop +- fixtures for injecting unused tcp ports +- pytest markers for treating tests as asyncio coroutines +- easy testing with non-default event loops +- support for `async def` fixtures and async generator fixtures + +Installation +------------ + +To install pytest-asyncio, simply: + +.. code-block:: bash + + $ pip install pytest-asyncio + +This is enough for pytest to pick up pytest-asyncio. + +Fixtures +-------- + +``event_loop`` +~~~~~~~~~~~~~~ +Creates and injects a new instance of the default asyncio event loop. By +default, the loop will be closed at the end of the test (i.e. the default +fixture scope is ``function``). + +Note that just using the ``event_loop`` fixture won't make your test function +a coroutine. You'll need to interact with the event loop directly, using methods +like ``event_loop.run_until_complete``. See the ``pytest.mark.asyncio`` marker +for treating test functions like coroutines. + +Simply using this fixture will not set the generated event loop as the +default asyncio event loop, or change the asyncio event loop policy in any way. +Use ``pytest.mark.asyncio`` for this purpose. + +.. code-block:: python + + def test_http_client(event_loop): + url = 'http://httpbin.org/get' + resp = event_loop.run_until_complete(http_client(url)) + assert b'HTTP/1.1 200 OK' in resp + +This fixture can be easily overridden in any of the standard pytest locations +(e.g. directly in the test file, or in ``conftest.py``) to use a non-default +event loop. This will take effect even if you're using the +``pytest.mark.asyncio`` marker and not the ``event_loop`` fixture directly. + +.. code-block:: python + + @pytest.fixture + def event_loop(): + loop = MyCustomLoop() + yield loop + loop.close() + +If the ``pytest.mark.asyncio`` marker is applied, a pytest hook will +ensure the produced loop is set as the default global loop. +Fixtures depending on the ``event_loop`` fixture can expect the policy to be properly modified when they run. + +``unused_tcp_port`` +~~~~~~~~~~~~~~~~~~~ +Finds and yields a single unused TCP port on the localhost interface. Useful for +binding temporary test servers. + +``unused_tcp_port_factory`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A callable which returns a different unused TCP port each invocation. Useful +when several unused TCP ports are required in a test. + +.. code-block:: python + + def a_test(unused_tcp_port_factory): + port1, port2 = unused_tcp_port_factory(), unused_tcp_port_factory() + ... + +Async fixtures +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be coroutines or asynchronous generators. + +.. code-block:: python3 + + @pytest.fixture + async def async_gen_fixture(): + await asyncio.sleep(0.1) + yield 'a value' + + @pytest.fixture(scope='module') + async def async_fixture(): + return await asyncio.sleep(0.1) + +All scopes are supported, but if you use a non-function scope you will need +to redefine the ``event_loop`` fixture to have the same or broader scope. +Async fixtures need the event loop, and so must have the same or narrower scope +than the ``event_loop`` fixture. + +If you want to do this with Python 3.5, the ``yield`` statement must be replaced with ``await yield_()`` and the coroutine +function must be decorated with ``@async_generator``, like so: + +.. code-block:: python3 + + from async_generator import yield_, async_generator + + @pytest.fixture + @async_generator + async def async_gen_fixture(): + await asyncio.sleep(0.1) + await yield_('a value') + + +Markers +------- + +``pytest.mark.asyncio`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Mark your test coroutine with this marker and pytest will execute it as an +asyncio task using the event loop provided by the ``event_loop`` fixture. See +the introductory section for an example. + +The event loop used can be overriden by overriding the ``event_loop`` fixture +(see above). + +In order to make your test code a little more concise, the pytest |pytestmark|_ +feature can be used to mark entire modules or classes with this marker. +Only test coroutines will be affected (by default, coroutines prefixed by +``test_``), so, for example, fixtures are safe to define. + +.. code-block:: python + + import asyncio + import pytest + + # All test coroutines will be treated as marked. + pytestmark = pytest.mark.asyncio + + async def test_example(event_loop): + """No marker!""" + await asyncio.sleep(0, loop=event_loop) + +.. |pytestmark| replace:: ``pytestmark`` +.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules + +Changelog +--------- +0.13.0 (2020-06-24) +~~~~~~~~~~~~~~~~~~~ +- Fix `#162 <https://github.com/pytest-dev/pytest-asyncio/issues/162>`_, and ``event_loop`` fixture behavior now is coherent on all scopes. + `#164 <https://github.com/pytest-dev/pytest-asyncio/pull/164>`_ + +0.12.0 (2020-05-04) +~~~~~~~~~~~~~~~~~~~ +- Run the event loop fixture as soon as possible. This helps with fixtures that have an implicit dependency on the event loop. + `#156 <https://github.com/pytest-dev/pytest-asyncio/pull/156>`_ + +0.11.0 (2020-04-20) +~~~~~~~~~~~~~~~~~~~ +- Test on 3.8, drop 3.3 and 3.4. Stick to 0.10 for these versions. + `#152 <https://github.com/pytest-dev/pytest-asyncio/pull/152>`_ +- Use the new Pytest 5.4.0 Function API. We therefore depend on pytest >= 5.4.0. + `#142 <https://github.com/pytest-dev/pytest-asyncio/pull/142>`_ +- Better ``pytest.skip`` support. + `#126 <https://github.com/pytest-dev/pytest-asyncio/pull/126>`_ + +0.10.0 (2019-01-08) +~~~~~~~~~~~~~~~~~~~~ +- ``pytest-asyncio`` integrates with `Hypothesis <https://hypothesis.readthedocs.io>`_ + to support ``@given`` on async test functions using ``asyncio``. + `#102 <https://github.com/pytest-dev/pytest-asyncio/pull/102>`_ +- Pytest 4.1 support. + `#105 <https://github.com/pytest-dev/pytest-asyncio/pull/105>`_ + +0.9.0 (2018-07-28) +~~~~~~~~~~~~~~~~~~ +- Python 3.7 support. +- Remove ``event_loop_process_pool`` fixture and + ``pytest.mark.asyncio_process_pool`` marker (see + https://bugs.python.org/issue34075 for deprecation and removal details) + +0.8.0 (2017-09-23) +~~~~~~~~~~~~~~~~~~ +- Improve integration with other packages (like aiohttp) with more careful event loop handling. + `#64 <https://github.com/pytest-dev/pytest-asyncio/pull/64>`_ + +0.7.0 (2017-09-08) +~~~~~~~~~~~~~~~~~~ +- Python versions pre-3.6 can use the async_generator library for async fixtures. + `#62 <https://github.com/pytest-dev/pytest-asyncio/pull/62>` + + +0.6.0 (2017-05-28) +~~~~~~~~~~~~~~~~~~ +- Support for Python versions pre-3.5 has been dropped. +- ``pytestmark`` now works on both module and class level. +- The ``forbid_global_loop`` parameter has been removed. +- Support for async and async gen fixtures has been added. + `#45 <https://github.com/pytest-dev/pytest-asyncio/pull/45>`_ +- The deprecation warning regarding ``asyncio.async()`` has been fixed. + `#51 <https://github.com/pytest-dev/pytest-asyncio/pull/51>`_ + +0.5.0 (2016-09-07) +~~~~~~~~~~~~~~~~~~ +- Introduced a changelog. + `#31 <https://github.com/pytest-dev/pytest-asyncio/issues/31>`_ +- The ``event_loop`` fixture is again responsible for closing itself. + This makes the fixture slightly harder to correctly override, but enables + other fixtures to depend on it correctly. + `#30 <https://github.com/pytest-dev/pytest-asyncio/issues/30>`_ +- Deal with the event loop policy by wrapping a special pytest hook, + ``pytest_fixture_setup``. This allows setting the policy before fixtures + dependent on the ``event_loop`` fixture run, thus allowing them to take + advantage of the ``forbid_global_loop`` parameter. As a consequence of this, + we now depend on pytest 3.0. + `#29 <https://github.com/pytest-dev/pytest-asyncio/issues/29>`_ + + +0.4.1 (2016-06-01) +~~~~~~~~~~~~~~~~~~ +- Fix a bug preventing the propagation of exceptions from the plugin. + `#25 <https://github.com/pytest-dev/pytest-asyncio/issues/25>`_ + +0.4.0 (2016-05-30) +~~~~~~~~~~~~~~~~~~ +- Make ``event_loop`` fixtures simpler to override by closing them in the + plugin, instead of directly in the fixture. + `#21 <https://github.com/pytest-dev/pytest-asyncio/pull/21>`_ +- Introduce the ``forbid_global_loop`` parameter. + `#21 <https://github.com/pytest-dev/pytest-asyncio/pull/21>`_ + +0.3.0 (2015-12-19) +~~~~~~~~~~~~~~~~~~ +- Support for Python 3.5 ``async``/``await`` syntax. + `#17 <https://github.com/pytest-dev/pytest-asyncio/pull/17>`_ + +0.2.0 (2015-08-01) +~~~~~~~~~~~~~~~~~~ +- ``unused_tcp_port_factory`` fixture. + `#10 <https://github.com/pytest-dev/pytest-asyncio/issues/10>`_ + + +0.1.1 (2015-04-23) +~~~~~~~~~~~~~~~~~~ +Initial release. + + +Contributing +------------ +Contributions are very welcome. Tests can be run with ``tox``, please ensure +the coverage at least stays the same before you submit a pull request. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/PKG-INFO b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/PKG-INFO new file mode 100644 index 00000000000..bb7611bc1a7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/PKG-INFO @@ -0,0 +1,302 @@ +Metadata-Version: 2.1 +Name: pytest-asyncio +Version: 0.14.0 +Summary: Pytest support for asyncio. +Home-page: https://github.com/pytest-dev/pytest-asyncio +Author: Tin Tvrtković +Author-email: tinchester@gmail.com +License: Apache 2.0 +Description: pytest-asyncio: pytest support for asyncio + ========================================== + + .. image:: https://img.shields.io/pypi/v/pytest-asyncio.svg + :target: https://pypi.python.org/pypi/pytest-asyncio + .. image:: https://travis-ci.org/pytest-dev/pytest-asyncio.svg?branch=master + :target: https://travis-ci.org/pytest-dev/pytest-asyncio + .. image:: https://coveralls.io/repos/pytest-dev/pytest-asyncio/badge.svg + :target: https://coveralls.io/r/pytest-dev/pytest-asyncio + .. image:: https://img.shields.io/pypi/pyversions/pytest-asyncio.svg + :target: https://github.com/pytest-dev/pytest-asyncio + :alt: Supported Python versions + + pytest-asyncio is an Apache2 licensed library, written in Python, for testing + asyncio code with pytest. + + asyncio code is usually written in the form of coroutines, which makes it + slightly more difficult to test using normal testing tools. pytest-asyncio + provides useful fixtures and markers to make testing easier. + + .. code-block:: python + + @pytest.mark.asyncio + async def test_some_asyncio_code(): + res = await library.do_something() + assert b'expected result' == res + + pytest-asyncio has been strongly influenced by pytest-tornado_. + + .. _pytest-tornado: https://github.com/eugeniy/pytest-tornado + + Features + -------- + + - fixtures for creating and injecting versions of the asyncio event loop + - fixtures for injecting unused tcp ports + - pytest markers for treating tests as asyncio coroutines + - easy testing with non-default event loops + - support for `async def` fixtures and async generator fixtures + + Installation + ------------ + + To install pytest-asyncio, simply: + + .. code-block:: bash + + $ pip install pytest-asyncio + + This is enough for pytest to pick up pytest-asyncio. + + Fixtures + -------- + + ``event_loop`` + ~~~~~~~~~~~~~~ + Creates and injects a new instance of the default asyncio event loop. By + default, the loop will be closed at the end of the test (i.e. the default + fixture scope is ``function``). + + Note that just using the ``event_loop`` fixture won't make your test function + a coroutine. You'll need to interact with the event loop directly, using methods + like ``event_loop.run_until_complete``. See the ``pytest.mark.asyncio`` marker + for treating test functions like coroutines. + + Simply using this fixture will not set the generated event loop as the + default asyncio event loop, or change the asyncio event loop policy in any way. + Use ``pytest.mark.asyncio`` for this purpose. + + .. code-block:: python + + def test_http_client(event_loop): + url = 'http://httpbin.org/get' + resp = event_loop.run_until_complete(http_client(url)) + assert b'HTTP/1.1 200 OK' in resp + + This fixture can be easily overridden in any of the standard pytest locations + (e.g. directly in the test file, or in ``conftest.py``) to use a non-default + event loop. This will take effect even if you're using the + ``pytest.mark.asyncio`` marker and not the ``event_loop`` fixture directly. + + .. code-block:: python + + @pytest.fixture + def event_loop(): + loop = MyCustomLoop() + yield loop + loop.close() + + If the ``pytest.mark.asyncio`` marker is applied, a pytest hook will + ensure the produced loop is set as the default global loop. + Fixtures depending on the ``event_loop`` fixture can expect the policy to be properly modified when they run. + + ``unused_tcp_port`` + ~~~~~~~~~~~~~~~~~~~ + Finds and yields a single unused TCP port on the localhost interface. Useful for + binding temporary test servers. + + ``unused_tcp_port_factory`` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + A callable which returns a different unused TCP port each invocation. Useful + when several unused TCP ports are required in a test. + + .. code-block:: python + + def a_test(unused_tcp_port_factory): + port1, port2 = unused_tcp_port_factory(), unused_tcp_port_factory() + ... + + Async fixtures + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be coroutines or asynchronous generators. + + .. code-block:: python3 + + @pytest.fixture + async def async_gen_fixture(): + await asyncio.sleep(0.1) + yield 'a value' + + @pytest.fixture(scope='module') + async def async_fixture(): + return await asyncio.sleep(0.1) + + All scopes are supported, but if you use a non-function scope you will need + to redefine the ``event_loop`` fixture to have the same or broader scope. + Async fixtures need the event loop, and so must have the same or narrower scope + than the ``event_loop`` fixture. + + If you want to do this with Python 3.5, the ``yield`` statement must be replaced with ``await yield_()`` and the coroutine + function must be decorated with ``@async_generator``, like so: + + .. code-block:: python3 + + from async_generator import yield_, async_generator + + @pytest.fixture + @async_generator + async def async_gen_fixture(): + await asyncio.sleep(0.1) + await yield_('a value') + + + Markers + ------- + + ``pytest.mark.asyncio`` + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Mark your test coroutine with this marker and pytest will execute it as an + asyncio task using the event loop provided by the ``event_loop`` fixture. See + the introductory section for an example. + + The event loop used can be overriden by overriding the ``event_loop`` fixture + (see above). + + In order to make your test code a little more concise, the pytest |pytestmark|_ + feature can be used to mark entire modules or classes with this marker. + Only test coroutines will be affected (by default, coroutines prefixed by + ``test_``), so, for example, fixtures are safe to define. + + .. code-block:: python + + import asyncio + import pytest + + # All test coroutines will be treated as marked. + pytestmark = pytest.mark.asyncio + + async def test_example(event_loop): + """No marker!""" + await asyncio.sleep(0, loop=event_loop) + + .. |pytestmark| replace:: ``pytestmark`` + .. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules + + Changelog + --------- + 0.13.0 (2020-06-24) + ~~~~~~~~~~~~~~~~~~~ + - Fix `#162 <https://github.com/pytest-dev/pytest-asyncio/issues/162>`_, and ``event_loop`` fixture behavior now is coherent on all scopes. + `#164 <https://github.com/pytest-dev/pytest-asyncio/pull/164>`_ + + 0.12.0 (2020-05-04) + ~~~~~~~~~~~~~~~~~~~ + - Run the event loop fixture as soon as possible. This helps with fixtures that have an implicit dependency on the event loop. + `#156 <https://github.com/pytest-dev/pytest-asyncio/pull/156>`_ + + 0.11.0 (2020-04-20) + ~~~~~~~~~~~~~~~~~~~ + - Test on 3.8, drop 3.3 and 3.4. Stick to 0.10 for these versions. + `#152 <https://github.com/pytest-dev/pytest-asyncio/pull/152>`_ + - Use the new Pytest 5.4.0 Function API. We therefore depend on pytest >= 5.4.0. + `#142 <https://github.com/pytest-dev/pytest-asyncio/pull/142>`_ + - Better ``pytest.skip`` support. + `#126 <https://github.com/pytest-dev/pytest-asyncio/pull/126>`_ + + 0.10.0 (2019-01-08) + ~~~~~~~~~~~~~~~~~~~~ + - ``pytest-asyncio`` integrates with `Hypothesis <https://hypothesis.readthedocs.io>`_ + to support ``@given`` on async test functions using ``asyncio``. + `#102 <https://github.com/pytest-dev/pytest-asyncio/pull/102>`_ + - Pytest 4.1 support. + `#105 <https://github.com/pytest-dev/pytest-asyncio/pull/105>`_ + + 0.9.0 (2018-07-28) + ~~~~~~~~~~~~~~~~~~ + - Python 3.7 support. + - Remove ``event_loop_process_pool`` fixture and + ``pytest.mark.asyncio_process_pool`` marker (see + https://bugs.python.org/issue34075 for deprecation and removal details) + + 0.8.0 (2017-09-23) + ~~~~~~~~~~~~~~~~~~ + - Improve integration with other packages (like aiohttp) with more careful event loop handling. + `#64 <https://github.com/pytest-dev/pytest-asyncio/pull/64>`_ + + 0.7.0 (2017-09-08) + ~~~~~~~~~~~~~~~~~~ + - Python versions pre-3.6 can use the async_generator library for async fixtures. + `#62 <https://github.com/pytest-dev/pytest-asyncio/pull/62>` + + + 0.6.0 (2017-05-28) + ~~~~~~~~~~~~~~~~~~ + - Support for Python versions pre-3.5 has been dropped. + - ``pytestmark`` now works on both module and class level. + - The ``forbid_global_loop`` parameter has been removed. + - Support for async and async gen fixtures has been added. + `#45 <https://github.com/pytest-dev/pytest-asyncio/pull/45>`_ + - The deprecation warning regarding ``asyncio.async()`` has been fixed. + `#51 <https://github.com/pytest-dev/pytest-asyncio/pull/51>`_ + + 0.5.0 (2016-09-07) + ~~~~~~~~~~~~~~~~~~ + - Introduced a changelog. + `#31 <https://github.com/pytest-dev/pytest-asyncio/issues/31>`_ + - The ``event_loop`` fixture is again responsible for closing itself. + This makes the fixture slightly harder to correctly override, but enables + other fixtures to depend on it correctly. + `#30 <https://github.com/pytest-dev/pytest-asyncio/issues/30>`_ + - Deal with the event loop policy by wrapping a special pytest hook, + ``pytest_fixture_setup``. This allows setting the policy before fixtures + dependent on the ``event_loop`` fixture run, thus allowing them to take + advantage of the ``forbid_global_loop`` parameter. As a consequence of this, + we now depend on pytest 3.0. + `#29 <https://github.com/pytest-dev/pytest-asyncio/issues/29>`_ + + + 0.4.1 (2016-06-01) + ~~~~~~~~~~~~~~~~~~ + - Fix a bug preventing the propagation of exceptions from the plugin. + `#25 <https://github.com/pytest-dev/pytest-asyncio/issues/25>`_ + + 0.4.0 (2016-05-30) + ~~~~~~~~~~~~~~~~~~ + - Make ``event_loop`` fixtures simpler to override by closing them in the + plugin, instead of directly in the fixture. + `#21 <https://github.com/pytest-dev/pytest-asyncio/pull/21>`_ + - Introduce the ``forbid_global_loop`` parameter. + `#21 <https://github.com/pytest-dev/pytest-asyncio/pull/21>`_ + + 0.3.0 (2015-12-19) + ~~~~~~~~~~~~~~~~~~ + - Support for Python 3.5 ``async``/``await`` syntax. + `#17 <https://github.com/pytest-dev/pytest-asyncio/pull/17>`_ + + 0.2.0 (2015-08-01) + ~~~~~~~~~~~~~~~~~~ + - ``unused_tcp_port_factory`` fixture. + `#10 <https://github.com/pytest-dev/pytest-asyncio/issues/10>`_ + + + 0.1.1 (2015-04-23) + ~~~~~~~~~~~~~~~~~~ + Initial release. + + + Contributing + ------------ + Contributions are very welcome. Tests can be run with ``tox``, please ensure + the coverage at least stays the same before you submit a pull request. + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Topic :: Software Development :: Testing +Classifier: Framework :: Pytest +Requires-Python: >= 3.5 +Provides-Extra: testing diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/SOURCES.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/SOURCES.txt new file mode 100644 index 00000000000..40cd5d7c7fc --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +LICENSE +README.rst +setup.cfg +setup.py +pytest_asyncio/__init__.py +pytest_asyncio/plugin.py +pytest_asyncio.egg-info/PKG-INFO +pytest_asyncio.egg-info/SOURCES.txt +pytest_asyncio.egg-info/dependency_links.txt +pytest_asyncio.egg-info/entry_points.txt +pytest_asyncio.egg-info/requires.txt +pytest_asyncio.egg-info/top_level.txt
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/dependency_links.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/dependency_links.txt new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/entry_points.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/entry_points.txt new file mode 100644 index 00000000000..69c53be38a8 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[pytest11] +asyncio = pytest_asyncio.plugin + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/requires.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/requires.txt new file mode 100644 index 00000000000..d546f122ad1 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/requires.txt @@ -0,0 +1,9 @@ +pytest>=5.4.0 + +[:python_version == "3.5"] +async_generator>=1.3 + +[testing] +async_generator>=1.3 +coverage +hypothesis>=5.7.1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/top_level.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/top_level.txt new file mode 100644 index 00000000000..08d05d1ecf4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/top_level.txt @@ -0,0 +1 @@ +pytest_asyncio diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/__init__.py new file mode 100644 index 00000000000..61c5f43b959 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/__init__.py @@ -0,0 +1,2 @@ +"""The main point for importing pytest-asyncio items.""" +__version__ = "0.14.0" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/plugin.py b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/plugin.py new file mode 100644 index 00000000000..2fdc5f4e774 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/pytest_asyncio/plugin.py @@ -0,0 +1,240 @@ +"""pytest-asyncio implementation.""" +import asyncio +import contextlib +import functools +import inspect +import socket + +import pytest +try: + from _pytest.python import transfer_markers +except ImportError: # Pytest 4.1.0 removes the transfer_marker api (#104) + def transfer_markers(*args, **kwargs): # noqa + """Noop when over pytest 4.1.0""" + pass + +try: + from async_generator import isasyncgenfunction +except ImportError: + from inspect import isasyncgenfunction + + +def _is_coroutine(obj): + """Check to see if an object is really an asyncio coroutine.""" + return asyncio.iscoroutinefunction(obj) or inspect.isgeneratorfunction(obj) + + +def pytest_configure(config): + """Inject documentation.""" + config.addinivalue_line("markers", + "asyncio: " + "mark the test as a coroutine, it will be " + "run using an asyncio event loop") + + +@pytest.mark.tryfirst +def pytest_pycollect_makeitem(collector, name, obj): + """A pytest hook to collect asyncio coroutines.""" + if collector.funcnamefilter(name) and _is_coroutine(obj): + item = pytest.Function.from_parent(collector, name=name) + + # Due to how pytest test collection works, module-level pytestmarks + # are applied after the collection step. Since this is the collection + # step, we look ourselves. + transfer_markers(obj, item.cls, item.module) + item = pytest.Function.from_parent(collector, name=name) # To reload keywords. + + if 'asyncio' in item.keywords: + return list(collector._genfunctions(name, obj)) + + +class FixtureStripper: + """Include additional Fixture, and then strip them""" + REQUEST = "request" + EVENT_LOOP = "event_loop" + + def __init__(self, fixturedef): + self.fixturedef = fixturedef + self.to_strip = set() + + def add(self, name): + """Add fixture name to fixturedef + and record in to_strip list (If not previously included)""" + if name in self.fixturedef.argnames: + return + self.fixturedef.argnames += (name, ) + self.to_strip.add(name) + + def get_and_strip_from(self, name, data_dict): + """Strip name from data, and return value""" + result = data_dict[name] + if name in self.to_strip: + del data_dict[name] + return result + +@pytest.hookimpl(trylast=True) +def pytest_fixture_post_finalizer(fixturedef, request): + """Called after fixture teardown""" + if fixturedef.argname == "event_loop": + # Set empty loop policy, so that subsequent get_event_loop() provides a new loop + asyncio.set_event_loop_policy(None) + + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef, request): + """Adjust the event loop policy when an event loop is produced.""" + if fixturedef.argname == "event_loop": + outcome = yield + loop = outcome.get_result() + policy = asyncio.get_event_loop_policy() + policy.set_event_loop(loop) + return + + if isasyncgenfunction(fixturedef.func): + # This is an async generator function. Wrap it accordingly. + generator = fixturedef.func + + fixture_stripper = FixtureStripper(fixturedef) + fixture_stripper.add(FixtureStripper.EVENT_LOOP) + fixture_stripper.add(FixtureStripper.REQUEST) + + + def wrapper(*args, **kwargs): + loop = fixture_stripper.get_and_strip_from(FixtureStripper.EVENT_LOOP, kwargs) + request = fixture_stripper.get_and_strip_from(FixtureStripper.REQUEST, kwargs) + + gen_obj = generator(*args, **kwargs) + + async def setup(): + res = await gen_obj.__anext__() + return res + + def finalizer(): + """Yield again, to finalize.""" + async def async_finalizer(): + try: + await gen_obj.__anext__() + except StopAsyncIteration: + pass + else: + msg = "Async generator fixture didn't stop." + msg += "Yield only once." + raise ValueError(msg) + loop.run_until_complete(async_finalizer()) + + request.addfinalizer(finalizer) + return loop.run_until_complete(setup()) + + fixturedef.func = wrapper + elif inspect.iscoroutinefunction(fixturedef.func): + coro = fixturedef.func + + fixture_stripper = FixtureStripper(fixturedef) + fixture_stripper.add(FixtureStripper.EVENT_LOOP) + + def wrapper(*args, **kwargs): + loop = fixture_stripper.get_and_strip_from(FixtureStripper.EVENT_LOOP, kwargs) + + async def setup(): + res = await coro(*args, **kwargs) + return res + + return loop.run_until_complete(setup()) + + fixturedef.func = wrapper + yield + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_pyfunc_call(pyfuncitem): + """ + Run asyncio marked test functions in an event loop instead of a normal + function call. + """ + if 'asyncio' in pyfuncitem.keywords: + if getattr(pyfuncitem.obj, 'is_hypothesis_test', False): + pyfuncitem.obj.hypothesis.inner_test = wrap_in_sync( + pyfuncitem.obj.hypothesis.inner_test, + _loop=pyfuncitem.funcargs['event_loop'] + ) + else: + pyfuncitem.obj = wrap_in_sync( + pyfuncitem.obj, + _loop=pyfuncitem.funcargs['event_loop'] + ) + yield + + +def wrap_in_sync(func, _loop): + """Return a sync wrapper around an async function executing it in the + current event loop.""" + + @functools.wraps(func) + def inner(**kwargs): + coro = func(**kwargs) + if coro is not None: + task = asyncio.ensure_future(coro, loop=_loop) + try: + _loop.run_until_complete(task) + except BaseException: + # run_until_complete doesn't get the result from exceptions + # that are not subclasses of `Exception`. Consume all + # exceptions to prevent asyncio's warning from logging. + if task.done() and not task.cancelled(): + task.exception() + raise + return inner + + +def pytest_runtest_setup(item): + if 'asyncio' in item.keywords: + # inject an event loop fixture for all async tests + if 'event_loop' in item.fixturenames: + item.fixturenames.remove('event_loop') + item.fixturenames.insert(0, 'event_loop') + if item.get_closest_marker("asyncio") is not None \ + and not getattr(item.obj, 'hypothesis', False) \ + and getattr(item.obj, 'is_hypothesis_test', False): + pytest.fail( + 'test function `%r` is using Hypothesis, but pytest-asyncio ' + 'only works with Hypothesis 3.64.0 or later.' % item + ) + + +@pytest.fixture +def event_loop(request): + """Create an instance of the default event loop for each test case.""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() + + +def _unused_tcp_port(): + """Find an unused localhost TCP port from 1024-65535 and return it.""" + with contextlib.closing(socket.socket()) as sock: + sock.bind(('127.0.0.1', 0)) + return sock.getsockname()[1] + + +@pytest.fixture +def unused_tcp_port(): + return _unused_tcp_port() + + +@pytest.fixture +def unused_tcp_port_factory(): + """A factory function, producing different unused TCP ports.""" + produced = set() + + def factory(): + """Return an unused port.""" + port = _unused_tcp_port() + + while port in produced: + port = _unused_tcp_port() + + produced.add(port) + + return port + return factory diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/setup.cfg b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/setup.cfg new file mode 100644 index 00000000000..23a8eba2ce5 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/setup.cfg @@ -0,0 +1,18 @@ +[coverage:run] +source = pytest_asyncio + +[coverage:report] +show_missing = true + +[tool:pytest] +addopts = -rsx --tb=short +testpaths = tests +filterwarnings = error + +[metadata] +license_file = LICENSE + +[egg_info] +tag_build = +tag_date = 0 + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/setup.py b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/setup.py new file mode 100644 index 00000000000..61757113507 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest-asyncio/setup.py @@ -0,0 +1,54 @@ +import re +from pathlib import Path + +from setuptools import setup, find_packages + + +def find_version(): + version_file = ( + Path(__file__) + .parent.joinpath("pytest_asyncio", "__init__.py") + .read_text() + ) + version_match = re.search( + r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M + ) + if version_match: + return version_match.group(1) + + raise RuntimeError("Unable to find version string.") + + +setup( + name="pytest-asyncio", + version=find_version(), + packages=find_packages(), + url="https://github.com/pytest-dev/pytest-asyncio", + license="Apache 2.0", + author="Tin Tvrtković", + author_email="tinchester@gmail.com", + description="Pytest support for asyncio.", + long_description=Path(__file__).parent.joinpath("README.rst").read_text(), + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Topic :: Software Development :: Testing", + "Framework :: Pytest", + ], + python_requires=">= 3.5", + install_requires=["pytest >= 5.4.0"], + extras_require={ + ':python_version == "3.5"': "async_generator >= 1.3", + "testing": [ + "coverage", + "async_generator >= 1.3", + "hypothesis >= 5.7.1", + ], + }, + entry_points={"pytest11": ["asyncio = pytest_asyncio.plugin"]}, +) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.coveragerc b/tests/wpt/web-platform-tests/tools/third_party/pytest/.coveragerc index cbc6c5c5084..09ab3764337 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.coveragerc +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.coveragerc @@ -16,3 +16,15 @@ source = src/ */lib/python*/site-packages/ */pypy*/site-packages/ *\Lib\site-packages\ + +[report] +skip_covered = True +show_missing = True +exclude_lines = + \#\s*pragma: no cover + ^\s*raise NotImplementedError\b + ^\s*return NotImplemented\b + ^\s*assert False(,|$) + + ^\s*if TYPE_CHECKING: + ^\s*@overload( |$) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.gitblameignore b/tests/wpt/web-platform-tests/tools/third_party/pytest/.gitblameignore new file mode 100644 index 00000000000..0cb298b024d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.gitblameignore @@ -0,0 +1,28 @@ +# List of revisions that can be ignored with git-blame(1). +# +# See `blame.ignoreRevsFile` in git-config(1) to enable it by default, or +# use it with `--ignore-revs-file` manually with git-blame. +# +# To "install" it: +# +# git config --local blame.ignoreRevsFile .gitblameignore + +# run black +703e4b11ba76171eccd3f13e723c47b810ded7ef +# switched to src layout +eaa882f3d5340956beb176aa1753e07e3f3f2190 +# pre-commit run pyupgrade --all-files +a91fe1feddbded535a4322ab854429e3a3961fb4 +# move node base classes from main to nodes +afc607cfd81458d4e4f3b1f3cf8cc931b933907e +# [?] split most fixture related code into own plugin +8c49561470708761f7321504f5e8343811be87ac +# run pyupgrade +9aacb4635e81edd6ecf281d4f6c0cfc8e94ab301 +# run blacken-docs +5f95dce95602921a70bfbc7d8de2f7712c5e4505 +# ran pyupgrade-docs again +75d0b899bbb56d6849e9d69d83a9426ed3f43f8b + +# move argument parser to own file +c9df77cbd6a365dcb73c39618e4842711817e871 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/FUNDING.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/FUNDING.yml index 1307445ebbd..5f2d1cf09c8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/FUNDING.yml +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/FUNDING.yml @@ -2,3 +2,4 @@ # * https://help.github.com/en/articles/displaying-a-sponsor-button-in-your-repository # * https://tidelift.com/subscription/how-to-connect-tidelift-with-github tidelift: pypi/pytest +open_collective: pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE.md b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index fb81416dd5e..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,10 +0,0 @@ -<!-- -Thanks for submitting an issue! - -Here's a quick checklist for what to provide: ---> - -- [ ] a detailed description of the bug or suggestion -- [ ] output of `pip list` from the virtual environment you are using -- [ ] pytest and operating system versions -- [ ] minimal example if possible diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/1_bug_report.md b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/1_bug_report.md new file mode 100644 index 00000000000..0fc3e06cd2c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/1_bug_report.md @@ -0,0 +1,16 @@ +--- +name: 🐛 Bug Report +about: Report errors and problems + +--- + +<!-- +Thanks for submitting an issue! + +Quick check-list while reporting bugs: +--> + +- [ ] a detailed description of the bug or problem you are having +- [ ] output of `pip list` from the virtual environment you are using +- [ ] pytest and operating system versions +- [ ] minimal example if possible diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/2_feature_request.md b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/2_feature_request.md new file mode 100644 index 00000000000..54912b05233 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/2_feature_request.md @@ -0,0 +1,5 @@ +--- +name: 🚀 Feature Request +about: Ideas for new features and improvements + +--- diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/config.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..742d2e4d668 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: ❓ Support Question + url: https://github.com/pytest-dev/pytest/discussions + about: Use GitHub's new Discussions feature for questions diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md index 590cc9958ae..9408fceafe3 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md @@ -2,15 +2,25 @@ Thanks for submitting a PR, your contribution is really appreciated! Here is a quick checklist that should be present in PRs. -(please delete this text from the final description, this is just a guideline) ---> -- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes. -- [ ] Target the `features` branch for new features and removals/deprecations. - [ ] Include documentation when adding new features. - [ ] Include new tests or update existing tests when applicable. +- [X] Allow maintainers to push and squash when merging my commits. Please uncheck this if you prefer to squash the commits yourself. + +If this change fixes an issue, please: -Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please: +- [ ] Add text like ``closes #XYZW`` to the PR description and/or commits (where ``XYZW`` is the issue number). See the [github docs](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for more information. + +Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please: - [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details. -- [ ] Add yourself to `AUTHORS` in alphabetical order; + + Write sentences in the **past or present tense**, examples: + + * *Improved verbose diff output with sequences.* + * *Terminal summary statistics now use multiple colors.* + + Also make sure to end the sentence with a `.`. + +- [ ] Add yourself to `AUTHORS` in alphabetical order. +--> diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/workflows/main.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/workflows/main.yml index a006fb092c8..9ef1a08b993 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/workflows/main.yml +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/workflows/main.yml @@ -3,13 +3,15 @@ name: main on: push: branches: - - 4.6.x + - master + - "[0-9]+.[0-9]+.x" tags: - "*" pull_request: branches: - - 4.6.x + - master + - "[0-9]+.[0-9]+.x" jobs: build: @@ -19,34 +21,30 @@ jobs: fail-fast: false matrix: name: [ - "windows-py27", "windows-py35", "windows-py36", "windows-py37", "windows-py37-pluggy", "windows-py38", - "ubuntu-py27-pluggy", - "ubuntu-py27-nobyte", + "ubuntu-py35", + "ubuntu-py36", "ubuntu-py37", "ubuntu-py37-pluggy", - "ubuntu-py37-pexpect-py37-twisted", "ubuntu-py37-freeze", - "ubuntu-pypy", + "ubuntu-py38", + "ubuntu-py39", "ubuntu-pypy3", - "macos-py27", + "macos-py37", "macos-py38", + "docs", + "doctesting", + "plugins", ] include: - # Windows jobs - - name: "windows-py27" - python: "2.7" - os: windows-latest - tox_env: "py27-xdist" - use_coverage: true - name: "windows-py35" python: "3.5" os: windows-latest @@ -56,86 +54,98 @@ jobs: python: "3.6" os: windows-latest tox_env: "py36-xdist" - use_coverage: true - name: "windows-py37" python: "3.7" os: windows-latest - tox_env: "py37-twisted-numpy" - use_coverage: true + tox_env: "py37-numpy" - name: "windows-py37-pluggy" python: "3.7" os: windows-latest tox_env: "py37-pluggymaster-xdist" - use_coverage: true - name: "windows-py38" python: "3.8" os: windows-latest - tox_env: "py38-xdist" + tox_env: "py38-unittestextras" use_coverage: true - # Ubuntu jobs – find the rest of them in .travis.yml - - name: "ubuntu-py27-pluggy" - python: "2.7" + - name: "ubuntu-py35" + python: "3.5" os: ubuntu-latest - tox_env: "py27-pluggymaster-xdist" - use_coverage: true - - name: "ubuntu-py27-nobyte" - python: "2.7" + tox_env: "py35-xdist" + - name: "ubuntu-py36" + python: "3.6" os: ubuntu-latest - tox_env: "py27-nobyte-numpy-xdist" - use_coverage: true + tox_env: "py36-xdist" - name: "ubuntu-py37" python: "3.7" os: ubuntu-latest - tox_env: "py37-lsof-numpy-xdist" + tox_env: "py37-lsof-numpy-oldattrs-pexpect" use_coverage: true - name: "ubuntu-py37-pluggy" python: "3.7" os: ubuntu-latest tox_env: "py37-pluggymaster-xdist" - use_coverage: true - - name: "ubuntu-py37-pexpect-py37-twisted" - python: "3.7" - os: ubuntu-latest - tox_env: "py37-pexpect,py37-twisted" - use_coverage: true - name: "ubuntu-py37-freeze" python: "3.7" os: ubuntu-latest tox_env: "py37-freeze" - - name: "ubuntu-pypy" - python: "pypy2" + - name: "ubuntu-py38" + python: "3.8" os: ubuntu-latest - tox_env: "pypy-xdist" - use_coverage: true + tox_env: "py38-xdist" + - name: "ubuntu-py39" + python: "3.9-dev" + os: ubuntu-latest + tox_env: "py39-xdist" - name: "ubuntu-pypy3" python: "pypy3" os: ubuntu-latest tox_env: "pypy3-xdist" - use_coverage: true - # MacOS jobs - - name: "macos-py27" - python: "2.7" + - name: "macos-py37" + python: "3.7" os: macos-latest - tox_env: "py27-xdist" - use_coverage: true + tox_env: "py37-xdist" - name: "macos-py38" python: "3.8" os: macos-latest tox_env: "py38-xdist" use_coverage: true + - name: "plugins" + python: "3.7" + os: ubuntu-latest + tox_env: "plugins" + + - name: "docs" + python: "3.7" + os: ubuntu-latest + tox_env: "docs" + - name: "doctesting" + python: "3.7" + os: ubuntu-latest + tox_env: "doctesting" + use_coverage: true + steps: - - uses: actions/checkout@v1 - - name: Set up Python ${{ matrix.python }} on ${{ matrix.os }} - uses: actions/setup-python@v1 + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v2 + if: matrix.python != '3.9-dev' + with: + python-version: ${{ matrix.python }} + - name: Set up Python ${{ matrix.python }} (deadsnakes) + uses: deadsnakes/action@v2.0.0 + if: matrix.python == '3.9-dev' with: python-version: ${{ matrix.python }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox coverage + - name: Test without coverage if: "! matrix.use_coverage" run: "tox -e ${{ matrix.tox_env }}" @@ -146,18 +156,33 @@ jobs: _PYTEST_TOX_COVERAGE_RUN: "coverage run -m" COVERAGE_PROCESS_START: ".coveragerc" _PYTEST_TOX_EXTRA_DEP: "coverage-enable-subprocess" - run: "tox -vv -e ${{ matrix.tox_env }}" + run: "tox -e ${{ matrix.tox_env }}" - name: Prepare coverage token if: (matrix.use_coverage && ( github.repository == 'pytest-dev/pytest' || github.event_name == 'pull_request' )) run: | python scripts/append_codecov_token.py + - name: Report coverage if: (matrix.use_coverage) env: CODECOV_NAME: ${{ matrix.name }} run: bash scripts/report-coverage.sh -F GHA,${{ runner.os }} + linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: set PY + run: echo "::set-env name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" + - uses: actions/cache@v1 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} + - run: pip install tox + - run: tox -e linting + deploy: if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest' @@ -166,9 +191,11 @@ jobs: needs: [build] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: "3.7" - name: Install dependencies diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/workflows/release-on-comment.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/workflows/release-on-comment.yml new file mode 100644 index 00000000000..94863d896b9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.github/workflows/release-on-comment.yml @@ -0,0 +1,31 @@ +# part of our release process, see `release-on-comment.py` +name: release on comment + +on: + issues: + types: [opened, edited] + issue_comment: + types: [created, edited] + +jobs: + build: + runs-on: ubuntu-latest + + if: (github.event.comment && startsWith(github.event.comment.body, '@pytestbot please')) || (github.event.issue && !github.event.comment && startsWith(github.event.issue.body, '@pytestbot please')) + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.8" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade setuptools tox + - name: Prepare release + run: | + tox -e release-on-comment -- $GITHUB_EVENT_PATH ${{ secrets.chatops }} diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.gitignore b/tests/wpt/web-platform-tests/tools/third_party/pytest/.gitignore index a008b436323..faea9eac03f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.gitignore +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.gitignore @@ -25,16 +25,20 @@ src/_pytest/_version.py doc/*/_build doc/*/.doctrees +doc/*/_changelog_towncrier_draft.rst build/ dist/ *.egg-info +htmlcov/ issue/ env/ .env/ +.venv/ 3rdparty/ .tox .cache .pytest_cache +.mypy_cache .coverage .coverage.* coverage.xml diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.pre-commit-config.yaml b/tests/wpt/web-platform-tests/tools/third_party/pytest/.pre-commit-config.yaml index 3aee45c62c5..6068a2d324d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.pre-commit-config.yaml +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.pre-commit-config.yaml @@ -1,59 +1,68 @@ -exclude: doc/en/example/py2py3/test_py2.py repos: - repo: https://github.com/psf/black rev: 19.10b0 hooks: - id: black args: [--safe, --quiet] - language_version: python3 - repo: https://github.com/asottile/blacken-docs - rev: v0.5.0 + rev: v1.7.0 hooks: - id: blacken-docs - additional_dependencies: [black==19.3b0] - language_version: python3 + additional_dependencies: [black==19.10b0] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.2.2 + rev: v3.1.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: fix-encoding-pragma + args: [--remove] - id: check-yaml - id: debug-statements - exclude: _pytest/debugging.py + exclude: _pytest/(debugging|hookspec).py language_version: python3 - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.7 + rev: 3.8.2 hooks: - id: flake8 language_version: python3 + additional_dependencies: + - flake8-typing-imports==1.9.0 + - flake8-docstrings==1.5.0 - repo: https://github.com/asottile/reorder_python_imports - rev: v1.4.0 + rev: v2.3.0 hooks: - id: reorder-python-imports - args: ['--application-directories=.:src'] + args: ['--application-directories=.:src', --py3-plus] - repo: https://github.com/asottile/pyupgrade - rev: v1.15.0 + rev: v2.4.4 hooks: - id: pyupgrade - args: [--keep-percent-format] -- repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.3.0 + args: [--py3-plus] +- repo: https://github.com/asottile/setup-cfg-fmt + rev: v1.9.0 + hooks: + - id: setup-cfg-fmt + # TODO: when upgrading setup-cfg-fmt this can be removed + args: [--max-py-version=3.9] +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.780 # NOTE: keep this in sync with setup.cfg. hooks: - - id: rst-backticks + - id: mypy + files: ^(src/|testing/) + args: [] - repo: local hooks: - id: rst name: rst entry: rst-lint --encoding utf-8 - files: ^(CHANGELOG.rst|HOWTORELEASE.rst|README.rst|TIDELIFT.rst|changelog/.*)$ + files: ^(RELEASING.rst|README.rst|TIDELIFT.rst)$ language: python additional_dependencies: [pygments, restructuredtext_lint] - id: changelogs-rst name: changelog filenames language: fail - entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst' - exclude: changelog/(\d+\.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst) + entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst' + exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst) files: ^changelog/ - id: py-deprecated name: py library is deprecated @@ -63,9 +72,11 @@ repos: _code\.| builtin\.| code\.| - io\.(BytesIO|saferepr)| + io\.| path\.local\.sysfind| process\.| - std\. + std\.| + error\.| + xml\. ) types: [python] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.readthedocs.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/.readthedocs.yml new file mode 100644 index 00000000000..0176c264001 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.readthedocs.yml @@ -0,0 +1,12 @@ +version: 2 + +python: + version: 3.7 + install: + - requirements: doc/en/requirements.txt + - method: pip + path: . + +formats: + - epub + - pdf diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/.travis.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/.travis.yml index d4f2d80483d..5c85dfe1f59 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/.travis.yml +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/.travis.yml @@ -1,12 +1,11 @@ language: python -dist: xenial -python: '3.7.4' +dist: trusty +python: '3.5.1' cache: false env: global: - - PYTEST_ADDOPTS="-vv --showlocals --durations=100 --exitfirst" - - PYTEST_COVERAGE=1 + - PYTEST_ADDOPTS=-vv # setuptools-scm needs all tags in order to obtain a proper version git: @@ -17,43 +16,11 @@ install: jobs: include: - # Coverage for: - # - TestArgComplete (linux only) - # - numpy - # - verbose=0 - - stage: baseline - env: TOXENV=py27-xdist - python: '2.7' - - - env: TOXENV=py38-xdist - python: '3.8' - - - stage: tests - # - _pytest.unittest._handle_skip (via pexpect). - env: TOXENV=py27-pexpect,py27-twisted - python: '2.7' - - - env: TOXENV=py35-xdist - python: '3.5.9' - - - env: TOXENV=py36-xdist PYTEST_REORDER_TESTS=0 - python: '3.6.9' - - - env: TOXENV=py37-numpy-pexpect-twisted - python: '3.7.4' - - # - test_sys_breakpoint_interception (via pexpect). - - env: TOXENV=py37-pexpect,py37-twisted - python: '3.7.4' - - # Run also non-verbosely, to gain coverage - - env: TOXENV=py38-xdist PYTEST_ADDOPTS="" - python: '3.8' - - - env: TOXENV=linting,docs,doctesting - cache: - directories: - - $HOME/.cache/pre-commit + # Coverage for Python 3.5.{0,1} specific code, mostly typing related. + - env: TOXENV=py35 PYTEST_COVERAGE=1 PYTEST_ADDOPTS="-k test_raises_cyclic_reference" + before_install: + # Work around https://github.com/jaraco/zipp/issues/40. + - python -m pip install -U 'setuptools>=34.4.0' virtualenv==16.7.9 before_script: - | @@ -68,13 +35,26 @@ before_script: export _PYTEST_TOX_COVERAGE_RUN="coverage run -m" export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess fi -script: env COLUMNS=120 python -m tox + +script: tox after_success: - | if [[ "$PYTEST_COVERAGE" = 1 ]]; then - env CODECOV_NAME="$TOXENV-$TRAVIS_OS_NAME" scripts/report-coverage.sh + env CODECOV_NAME="$TOXENV-$TRAVIS_OS_NAME" scripts/report-coverage.sh -F Travis fi + +notifications: + irc: + channels: + - "chat.freenode.net#pytest" + on_success: change + on_failure: change + skip_join: true + email: + - pytest-commit@python.org + branches: only: - - 4.6.x + - master + - /^\d+\.\d+\.x$/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/AUTHORS b/tests/wpt/web-platform-tests/tools/third_party/pytest/AUTHORS index 80fce539294..c8dfec4010a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/AUTHORS +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/AUTHORS @@ -15,6 +15,7 @@ Alexander Johnson Alexei Kozlenok Allan Feldman Aly Sivji +Amir Elkess Anatoly Bubenkoff Anders Hovmöller Andras Mitzki @@ -22,6 +23,7 @@ Andras Tim Andrea Cimatoribus Andreas Zeidler Andrey Paramonov +Andrzej Klajnert Andrzej Ostrowski Andy Freeland Anthon van der Neut @@ -50,31 +52,38 @@ Carl Friedrich Bolz Carlos Jenkins Ceridwen Charles Cloud +Charles Machalow Charnjit SiNGH (CCSJ) Chris Lamb Christian Boelsen Christian Fetzer +Christian Neumüller Christian Theunert Christian Tismer -Christopher Gilling +Christoph Buelter Christopher Dignam +Christopher Gilling +Claire Cecil Claudio Madotto CrazyMerlyn Cyrus Maden Damian Skrzypczak -Dhiren Serai Daniel Grana Daniel Hahler Daniel Nuri Daniel Wandschneider Danielle Jenkins +Daniil Galiev Dave Hunt David Díaz-Barquero David Mohr +David Paul Röthlisberger David Szotten David Vierra Daw-Ran Liou +Debi Mishra Denis Kirisov +Dhiren Serai Diego Russo Dmitry Dygalo Dmitry Pribysh @@ -87,25 +96,31 @@ Elizaveta Shashkova Endre Galaczi Eric Hunsberger Eric Siegerman +Erik Aronesty Erik M. Bray Evan Kepner Fabien Zarifian Fabio Zadrozny +Felix Nieuwenhuizen Feng Ma -Fernando Mezzabotta Rey Florian Bruhin +Florian Dahlitz Floris Bruynooghe Gabriel Reis +Gene Wood George Kussumoto Georgy Dyuldin +Gleb Nikonorov Graham Horler Greg Price +Gregory Lee Grig Gheorghiu Grigorii Eremeev (budulianin) Guido Wesdorp Guoqiang Zhang Harald Armin Massa Henk-Jaap Wagenaar +Holger Kohr Hugo van Kemenade Hui Wang (coldnight) Ian Bicking @@ -114,6 +129,7 @@ Ilya Konstantinov Ionuț Turturică Iwan Briquemont Jaap Broekhuizen +Jakub Mitoraj Jan Balster Janne Vanhala Jason R. Coombs @@ -130,12 +146,17 @@ Jordan Guymon Jordan Moldow Jordan Speicher Joseph Hunkeler +Josh Karpel Joshua Bronson Jurko Gospodnetić Justyna Janczyszyn Kale Kundert +Kamran Ahmad +Karl O. Pinc Katarzyna Jachim +Katarzyna Król Katerina Koukiou +Keri Volans Kevin Cox Kevin J. Foley Kodi B. Arfer @@ -145,6 +166,7 @@ Kyle Altendorf Lawrence Mitchell Lee Kamentsky Lev Maximov +Lewis Cowles Llandy Riveron Del Risco Loic Esteve Lukas Bednar @@ -157,7 +179,9 @@ Manuel Krebber Marc Schlaich Marcelo Duarte Trevisani Marcin Bachry +Marco Gorelli Mark Abramowitz +Mark Dickinson Markus Unterwaditzer Martijn Faassen Martin Altmayer @@ -169,16 +193,20 @@ Matt Duck Matt Williams Matthias Hafner Maxim Filipenko +Maximilian Cosmo Sitter mbyt Michael Aquilina Michael Birtwell Michael Droettboom +Michael Goerz +Michael Krebs Michael Seifert Michal Wajszczuk Mihai Capotă Mike Hoyle (hoylemd) Mike Lundy Miro Hrončok +Nathaniel Compton Nathaniel Waisbrot Ned Batchelder Neven Mundar @@ -195,25 +223,34 @@ Omer Hadari Ondřej Súkup Oscar Benjamin Patrick Hayes +Pauli Virtanen +Pavel Karateev Paweł Adamczak Pedro Algarvio +Philipp Loose Pieter Mulder Piotr Banaszkiewicz +Piotr Helm +Prashant Anand Pulkit Goyal Punyashloka Biswal Quentin Pradet Ralf Schmitt +Ram Rachum +Ralph Giles Ran Benita Raphael Castaneda Raphael Pierzina Raquel Alegre Ravi Chandra +Robert Holt Roberto Polli Roland Puntaier Romain Dorgueil Roman Bolshakov Ronny Pfannschmidt Ross Lawley +Ruaridh Williamson Russel Winder Ryan Wooden Samuel Dion-Girardeau @@ -222,15 +259,19 @@ Samuele Pedroni Sankt Petersbug Segev Finer Serhii Mozghovyi +Seth Junot Simon Gomizelj +Simon Kerr Skylar Downes Srinivas Reddy Thatiparthy Stefan Farmbauer +Stefan Scherfke Stefan Zimmermann Stefano Taschini Steffen Allner Stephan Obermann Sven-Hendrik Haase +Sylvain Marié Tadek Teleżyński Takafumi Arakaki Tarcisio Fischer @@ -238,10 +279,13 @@ Tareq Alayan Ted Xiao Thomas Grainger Thomas Hisch +Tim Hoffmann Tim Strazny Tom Dalton Tom Viner +Tomáš Gavenčiak Tomer Keren +Tor Colvin Trevor Bekolay Tyler Goodlet Tzu-ping Chung @@ -252,12 +296,17 @@ Vidar T. Fauske Virgil Dupras Vitaly Lashmanov Vlad Dragos +Vlad Radziuk +Vladyslav Rachek Volodymyr Piskun +Wei Lin Wil Cooley William Lee Wim Glenn Wouter van Ackooy +Xixi Zhao Xuan Luong Xuecong Liao +Yoav Caspi Zac Hatfield-Dodds Zoltán Máté diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/CHANGELOG.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/CHANGELOG.rst index 89437663f59..3865f250c26 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/CHANGELOG.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/CHANGELOG.rst @@ -1,6872 +1,7 @@ -================= -Changelog history -================= +========= +Changelog +========= -Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``). +The pytest CHANGELOG is located `here <https://docs.pytest.org/en/stable/changelog.html>`__. -Backward incompatible (breaking) changes will only be introduced in major versions -with advance notice in the **Deprecations** section of releases. - - -.. - You should *NOT* be adding new change log entries to this file, this - file is managed by towncrier. You *may* edit previous change logs to - fix problems like typo corrections or such. - To add a new change log entry, please see - https://pip.pypa.io/en/latest/development/#adding-a-news-entry - we named the news folder changelog - -.. towncrier release notes start - -pytest 4.6.11 (2020-06-04) -========================== - -Bug Fixes ---------- - -- `#6334 <https://github.com/pytest-dev/pytest/issues/6334>`_: Fix summary entries appearing twice when ``f/F`` and ``s/S`` report chars were used at the same time in the ``-r`` command-line option (for example ``-rFf``). - - The upper case variants were never documented and the preferred form should be the lower case. - - -- `#7310 <https://github.com/pytest-dev/pytest/issues/7310>`_: Fix ``UnboundLocalError: local variable 'letter' referenced before - assignment`` in ``_pytest.terminal.pytest_report_teststatus()`` - when plugins return report objects in an unconventional state. - - This was making ``pytest_report_teststatus()`` skip - entering if-block branches that declare the ``letter`` variable. - - The fix was to set the initial value of the ``letter`` before - the if-block cascade so that it always has a value. - - -pytest 4.6.10 (2020-05-08) -========================== - -Features --------- - -- `#6870 <https://github.com/pytest-dev/pytest/issues/6870>`_: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``. - - Remark: while this is technically a new feature and according to our `policy <https://docs.pytest.org/en/latest/py27-py34-deprecation.html#what-goes-into-4-6-x-releases>`_ it should not have been backported, we have opened an exception in this particular case because it fixes a serious interaction with ``pytest-xdist``, so it can also be considered a bugfix. - -Trivial/Internal Changes ------------------------- - -- `#6404 <https://github.com/pytest-dev/pytest/issues/6404>`_: Remove usage of ``parser`` module, deprecated in Python 3.9. - - -pytest 4.6.9 (2020-01-04) -========================= - -Bug Fixes ---------- - -- `#6301 <https://github.com/pytest-dev/pytest/issues/6301>`_: Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``). - - -pytest 4.6.8 (2019-12-19) -========================= - -Features --------- - -- `#5471 <https://github.com/pytest-dev/pytest/issues/5471>`_: JUnit XML now includes a timestamp and hostname in the testsuite tag. - - - -Bug Fixes ---------- - -- `#5430 <https://github.com/pytest-dev/pytest/issues/5430>`_: junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. - - - -Trivial/Internal Changes ------------------------- - -- `#6345 <https://github.com/pytest-dev/pytest/issues/6345>`_: Pin ``colorama`` to ``0.4.1`` only for Python 3.4 so newer Python versions can still receive colorama updates. - - -pytest 4.6.7 (2019-12-05) -========================= - -Bug Fixes ---------- - -- `#5477 <https://github.com/pytest-dev/pytest/issues/5477>`_: The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element. - - -- `#6044 <https://github.com/pytest-dev/pytest/issues/6044>`_: Properly ignore ``FileNotFoundError`` (``OSError.errno == NOENT`` in Python 2) exceptions when trying to remove old temporary directories, - for instance when multiple processes try to remove the same directory (common with ``pytest-xdist`` - for example). - - -pytest 4.6.6 (2019-10-11) -========================= - -Bug Fixes ---------- - -- `#5523 <https://github.com/pytest-dev/pytest/issues/5523>`_: Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+. - - -- `#5537 <https://github.com/pytest-dev/pytest/issues/5537>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the - standard library on Python 3.8+. - - -- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text". - - -- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``. - - - -Trivial/Internal Changes ------------------------- - -- `#5801 <https://github.com/pytest-dev/pytest/issues/5801>`_: Fixes python version checks (detected by ``flake8-2020``) in case python4 becomes a thing. - - -pytest 4.6.5 (2019-08-05) -========================= - -Bug Fixes ---------- - -- `#4344 <https://github.com/pytest-dev/pytest/issues/4344>`_: Fix RuntimeError/StopIteration when trying to collect package with "__init__.py" only. - - -- `#5478 <https://github.com/pytest-dev/pytest/issues/5478>`_: Fix encode error when using unicode strings in exceptions with ``pytest.raises``. - - -- `#5524 <https://github.com/pytest-dev/pytest/issues/5524>`_: Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only, - which could lead to pytest crashing when executed a second time with the ``--basetemp`` option. - - -- `#5547 <https://github.com/pytest-dev/pytest/issues/5547>`_: ``--step-wise`` now handles ``xfail(strict=True)`` markers properly. - - -- `#5650 <https://github.com/pytest-dev/pytest/issues/5650>`_: Improved output when parsing an ini configuration file fails. - - -pytest 4.6.4 (2019-06-28) -========================= - -Bug Fixes ---------- - -- `#5404 <https://github.com/pytest-dev/pytest/issues/5404>`_: Emit a warning when attempting to unwrap a broken object raises an exception, - for easier debugging (`#5080 <https://github.com/pytest-dev/pytest/issues/5080>`__). - - -- `#5444 <https://github.com/pytest-dev/pytest/issues/5444>`_: Fix ``--stepwise`` mode when the first file passed on the command-line fails to collect. - - -- `#5482 <https://github.com/pytest-dev/pytest/issues/5482>`_: Fix bug introduced in 4.6.0 causing collection errors when passing - more than 2 positional arguments to ``pytest.mark.parametrize``. - - -- `#5505 <https://github.com/pytest-dev/pytest/issues/5505>`_: Fix crash when discovery fails while using ``-p no:terminal``. - - -pytest 4.6.3 (2019-06-11) -========================= - -Bug Fixes ---------- - -- `#5383 <https://github.com/pytest-dev/pytest/issues/5383>`_: ``-q`` has again an impact on the style of the collected items - (``--collect-only``) when ``--log-cli-level`` is used. - - -- `#5389 <https://github.com/pytest-dev/pytest/issues/5389>`_: Fix regressions of `#5063 <https://github.com/pytest-dev/pytest/pull/5063>`__ for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``. - - -- `#5390 <https://github.com/pytest-dev/pytest/issues/5390>`_: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods. - - -pytest 4.6.2 (2019-06-03) -========================= - -Bug Fixes ---------- - -- `#5370 <https://github.com/pytest-dev/pytest/issues/5370>`_: Revert unrolling of ``all()`` to fix ``NameError`` on nested comprehensions. - - -- `#5371 <https://github.com/pytest-dev/pytest/issues/5371>`_: Revert unrolling of ``all()`` to fix incorrect handling of generators with ``if``. - - -- `#5372 <https://github.com/pytest-dev/pytest/issues/5372>`_: Revert unrolling of ``all()`` to fix incorrect assertion when using ``all()`` in an expression. - - -pytest 4.6.1 (2019-06-02) -========================= - -Bug Fixes ---------- - -- `#5354 <https://github.com/pytest-dev/pytest/issues/5354>`_: Fix ``pytest.mark.parametrize`` when the argvalues is an iterator. - - -- `#5358 <https://github.com/pytest-dev/pytest/issues/5358>`_: Fix assertion rewriting of ``all()`` calls to deal with non-generators. - - -pytest 4.6.0 (2019-05-31) -========================= - -Important ---------- - -The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**. - -For more details, see our `Python 2.7 and 3.4 support plan <https://docs.pytest.org/en/latest/py27-py34-deprecation.html>`__. - - -Features --------- - -- `#4559 <https://github.com/pytest-dev/pytest/issues/4559>`_: Added the ``junit_log_passing_tests`` ini value which can be used to enable or disable logging of passing test output in the Junit XML file. - - -- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: pytester's ``testdir.spawn`` uses ``tmpdir`` as HOME/USERPROFILE directory. - - -- `#5062 <https://github.com/pytest-dev/pytest/issues/5062>`_: Unroll calls to ``all`` to full for-loops with assertion rewriting for better failure messages, especially when using Generator Expressions. - - -- `#5063 <https://github.com/pytest-dev/pytest/issues/5063>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time. - - -- `#5091 <https://github.com/pytest-dev/pytest/issues/5091>`_: The output for ini options in ``--help`` has been improved. - - -- `#5269 <https://github.com/pytest-dev/pytest/issues/5269>`_: ``pytest.importorskip`` includes the ``ImportError`` now in the default ``reason``. - - -- `#5311 <https://github.com/pytest-dev/pytest/issues/5311>`_: Captured logs that are output for each failing test are formatted using the - ColoredLevelFormatter. - - -- `#5312 <https://github.com/pytest-dev/pytest/issues/5312>`_: Improved formatting of multiline log messages in Python 3. - - - -Bug Fixes ---------- - -- `#2064 <https://github.com/pytest-dev/pytest/issues/2064>`_: The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now. - - -- `#4908 <https://github.com/pytest-dev/pytest/issues/4908>`_: The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``). - - -- `#5036 <https://github.com/pytest-dev/pytest/issues/5036>`_: Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized. - - -- `#5256 <https://github.com/pytest-dev/pytest/issues/5256>`_: Handle internal error due to a lone surrogate unicode character not being representable in Jython. - - -- `#5257 <https://github.com/pytest-dev/pytest/issues/5257>`_: Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream. - - -- `#5278 <https://github.com/pytest-dev/pytest/issues/5278>`_: Pytest's internal python plugin can be disabled using ``-p no:python`` again. - - -- `#5286 <https://github.com/pytest-dev/pytest/issues/5286>`_: Fix issue with ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option not working when using a list of test IDs in parametrized tests. - - -- `#5330 <https://github.com/pytest-dev/pytest/issues/5330>`_: Show the test module being collected when emitting ``PytestCollectionWarning`` messages for - test classes with ``__init__`` and ``__new__`` methods to make it easier to pin down the problem. - - -- `#5333 <https://github.com/pytest-dev/pytest/issues/5333>`_: Fix regression in 4.5.0 with ``--lf`` not re-running all tests with known failures from non-selected tests. - - - -Improved Documentation ----------------------- - -- `#5250 <https://github.com/pytest-dev/pytest/issues/5250>`_: Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``. - - -pytest 4.5.0 (2019-05-11) -========================= - -Features --------- - -- `#4826 <https://github.com/pytest-dev/pytest/issues/4826>`_: A warning is now emitted when unknown marks are used as a decorator. - This is often due to a typo, which can lead to silently broken tests. - - -- `#4907 <https://github.com/pytest-dev/pytest/issues/4907>`_: Show XFail reason as part of JUnitXML message field. - - -- `#5013 <https://github.com/pytest-dev/pytest/issues/5013>`_: Messages from crash reports are displayed within test summaries now, truncated to the terminal width. - - -- `#5023 <https://github.com/pytest-dev/pytest/issues/5023>`_: New flag ``--strict-markers`` that triggers an error when unknown markers (e.g. those not registered using the `markers option`_ in the configuration file) are used in the test suite. - - The existing ``--strict`` option has the same behavior currently, but can be augmented in the future for additional checks. - - .. _`markers option`: https://docs.pytest.org/en/latest/reference.html#confval-markers - - -- `#5026 <https://github.com/pytest-dev/pytest/issues/5026>`_: Assertion failure messages for sequences and dicts contain the number of different items now. - - -- `#5034 <https://github.com/pytest-dev/pytest/issues/5034>`_: Improve reporting with ``--lf`` and ``--ff`` (run-last-failure). - - -- `#5035 <https://github.com/pytest-dev/pytest/issues/5035>`_: The ``--cache-show`` option/action accepts an optional glob to show only matching cache entries. - - -- `#5059 <https://github.com/pytest-dev/pytest/issues/5059>`_: Standard input (stdin) can be given to pytester's ``Testdir.run()`` and ``Testdir.popen()``. - - -- `#5068 <https://github.com/pytest-dev/pytest/issues/5068>`_: The ``-r`` option learnt about ``A`` to display all reports (including passed ones) in the short test summary. - - -- `#5108 <https://github.com/pytest-dev/pytest/issues/5108>`_: The short test summary is displayed after passes with output (``-rP``). - - -- `#5172 <https://github.com/pytest-dev/pytest/issues/5172>`_: The ``--last-failed`` (``--lf``) option got smarter and will now skip entire files if all tests - of that test file have passed in previous runs, greatly speeding up collection. - - -- `#5177 <https://github.com/pytest-dev/pytest/issues/5177>`_: Introduce new specific warning ``PytestWarning`` subclasses to make it easier to filter warnings based on the class, rather than on the message. The new subclasses are: - - - * ``PytestAssertRewriteWarning`` - - * ``PytestCacheWarning`` - - * ``PytestCollectionWarning`` - - * ``PytestConfigWarning`` - - * ``PytestUnhandledCoroutineWarning`` - - * ``PytestUnknownMarkWarning`` - - -- `#5202 <https://github.com/pytest-dev/pytest/issues/5202>`_: New ``record_testsuite_property`` session-scoped fixture allows users to log ``<property>`` tags at the ``testsuite`` - level with the ``junitxml`` plugin. - - The generated XML is compatible with the latest xunit standard, contrary to - the properties recorded by ``record_property`` and ``record_xml_attribute``. - - -- `#5214 <https://github.com/pytest-dev/pytest/issues/5214>`_: The default logging format has been changed to improve readability. Here is an - example of a previous logging message:: - - test_log_cli_enabled_disabled.py 3 CRITICAL critical message logged by test - - This has now become:: - - CRITICAL root:test_log_cli_enabled_disabled.py:3 critical message logged by test - - The formatting can be changed through the `log_format <https://docs.pytest.org/en/latest/reference.html#confval-log_format>`__ configuration option. - - -- `#5220 <https://github.com/pytest-dev/pytest/issues/5220>`_: ``--fixtures`` now also shows fixture scope for scopes other than ``"function"``. - - - -Bug Fixes ---------- - -- `#5113 <https://github.com/pytest-dev/pytest/issues/5113>`_: Deselected items from plugins using ``pytest_collect_modifyitems`` as a hookwrapper are correctly reported now. - - -- `#5144 <https://github.com/pytest-dev/pytest/issues/5144>`_: With usage errors ``exitstatus`` is set to ``EXIT_USAGEERROR`` in the ``pytest_sessionfinish`` hook now as expected. - - -- `#5235 <https://github.com/pytest-dev/pytest/issues/5235>`_: ``outcome.exit`` is not used with ``EOF`` in the pdb wrapper anymore, but only with ``quit``. - - - -Improved Documentation ----------------------- - -- `#4935 <https://github.com/pytest-dev/pytest/issues/4935>`_: Expand docs on registering marks and the effect of ``--strict``. - - - -Trivial/Internal Changes ------------------------- - -- `#4942 <https://github.com/pytest-dev/pytest/issues/4942>`_: ``logging.raiseExceptions`` is not set to ``False`` anymore. - - -- `#5013 <https://github.com/pytest-dev/pytest/issues/5013>`_: pytest now depends on `wcwidth <https://pypi.org/project/wcwidth>`__ to properly track unicode character sizes for more precise terminal output. - - -- `#5059 <https://github.com/pytest-dev/pytest/issues/5059>`_: pytester's ``Testdir.popen()`` uses ``stdout`` and ``stderr`` via keyword arguments with defaults now (``subprocess.PIPE``). - - -- `#5069 <https://github.com/pytest-dev/pytest/issues/5069>`_: The code for the short test summary in the terminal was moved to the terminal plugin. - - -- `#5082 <https://github.com/pytest-dev/pytest/issues/5082>`_: Improved validation of kwargs for various methods in the pytester plugin. - - -- `#5202 <https://github.com/pytest-dev/pytest/issues/5202>`_: ``record_property`` now emits a ``PytestWarning`` when used with ``junit_family=xunit2``: the fixture generates - ``property`` tags as children of ``testcase``, which is not permitted according to the most - `recent schema <https://github.com/jenkinsci/xunit-plugin/blob/master/ - src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd>`__. - - -- `#5239 <https://github.com/pytest-dev/pytest/issues/5239>`_: Pin ``pluggy`` to ``< 1.0`` so we don't update to ``1.0`` automatically when - it gets released: there are planned breaking changes, and we want to ensure - pytest properly supports ``pluggy 1.0``. - - -pytest 4.4.2 (2019-05-08) -========================= - -Bug Fixes ---------- - -- `#5089 <https://github.com/pytest-dev/pytest/issues/5089>`_: Fix crash caused by error in ``__repr__`` function with both ``showlocals`` and verbose output enabled. - - -- `#5139 <https://github.com/pytest-dev/pytest/issues/5139>`_: Eliminate core dependency on 'terminal' plugin. - - -- `#5229 <https://github.com/pytest-dev/pytest/issues/5229>`_: Require ``pluggy>=0.11.0`` which reverts a dependency to ``importlib-metadata`` added in ``0.10.0``. - The ``importlib-metadata`` package cannot be imported when installed as an egg and causes issues when relying on ``setup.py`` to install test dependencies. - - - -Improved Documentation ----------------------- - -- `#5171 <https://github.com/pytest-dev/pytest/issues/5171>`_: Doc: ``pytest_ignore_collect``, ``pytest_collect_directory``, ``pytest_collect_file`` and ``pytest_pycollect_makemodule`` hooks's 'path' parameter documented type is now ``py.path.local`` - - -- `#5188 <https://github.com/pytest-dev/pytest/issues/5188>`_: Improve help for ``--runxfail`` flag. - - - -Trivial/Internal Changes ------------------------- - -- `#5182 <https://github.com/pytest-dev/pytest/issues/5182>`_: Removed internal and unused ``_pytest.deprecated.MARK_INFO_ATTRIBUTE``. - - -pytest 4.4.1 (2019-04-15) -========================= - -Bug Fixes ---------- - -- `#5031 <https://github.com/pytest-dev/pytest/issues/5031>`_: Environment variables are properly restored when using pytester's ``testdir`` fixture. - - -- `#5039 <https://github.com/pytest-dev/pytest/issues/5039>`_: Fix regression with ``--pdbcls``, which stopped working with local modules in 4.0.0. - - -- `#5092 <https://github.com/pytest-dev/pytest/issues/5092>`_: Produce a warning when unknown keywords are passed to ``pytest.param(...)``. - - -- `#5098 <https://github.com/pytest-dev/pytest/issues/5098>`_: Invalidate import caches with ``monkeypatch.syspath_prepend``, which is required with namespace packages being used. - - -pytest 4.4.0 (2019-03-29) -========================= - -Features --------- - -- `#2224 <https://github.com/pytest-dev/pytest/issues/2224>`_: ``async`` test functions are skipped and a warning is emitted when a suitable - async plugin is not installed (such as ``pytest-asyncio`` or ``pytest-trio``). - - Previously ``async`` functions would not execute at all but still be marked as "passed". - - -- `#2482 <https://github.com/pytest-dev/pytest/issues/2482>`_: Include new ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option to disable ascii-escaping in parametrized values. This may cause a series of problems and as the name makes clear, use at your own risk. - - -- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: The ``-p`` option can now be used to early-load plugins also by entry-point name, instead of just - by module name. - - This makes it possible to early load external plugins like ``pytest-cov`` in the command-line:: - - pytest -p pytest_cov - - -- `#4855 <https://github.com/pytest-dev/pytest/issues/4855>`_: The ``--pdbcls`` option handles classes via module attributes now (e.g. - ``pdb:pdb.Pdb`` with `pdb++`_), and its validation was improved. - - .. _pdb++: https://pypi.org/project/pdbpp/ - - -- `#4875 <https://github.com/pytest-dev/pytest/issues/4875>`_: The `testpaths <https://docs.pytest.org/en/latest/reference.html#confval-testpaths>`__ configuration option is now displayed next - to the ``rootdir`` and ``inifile`` lines in the pytest header if the option is in effect, i.e., directories or file names were - not explicitly passed in the command line. - - Also, ``inifile`` is only displayed if there's a configuration file, instead of an empty ``inifile:`` string. - - -- `#4911 <https://github.com/pytest-dev/pytest/issues/4911>`_: Doctests can be skipped now dynamically using ``pytest.skip()``. - - -- `#4920 <https://github.com/pytest-dev/pytest/issues/4920>`_: Internal refactorings have been made in order to make the implementation of the - `pytest-subtests <https://github.com/pytest-dev/pytest-subtests>`__ plugin - possible, which adds unittest sub-test support and a new ``subtests`` fixture as discussed in - `#1367 <https://github.com/pytest-dev/pytest/issues/1367>`__. - - For details on the internal refactorings, please see the details on the related PR. - - -- `#4931 <https://github.com/pytest-dev/pytest/issues/4931>`_: pytester's ``LineMatcher`` asserts that the passed lines are a sequence. - - -- `#4936 <https://github.com/pytest-dev/pytest/issues/4936>`_: Handle ``-p plug`` after ``-p no:plug``. - - This can be used to override a blocked plugin (e.g. in "addopts") from the - command line etc. - - -- `#4951 <https://github.com/pytest-dev/pytest/issues/4951>`_: Output capturing is handled correctly when only capturing via fixtures (capsys, capfs) with ``pdb.set_trace()``. - - -- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: ``pytester`` sets ``$HOME`` and ``$USERPROFILE`` to the temporary directory during test runs. - - This ensures to not load configuration files from the real user's home directory. - - -- `#4980 <https://github.com/pytest-dev/pytest/issues/4980>`_: Namespace packages are handled better with ``monkeypatch.syspath_prepend`` and ``testdir.syspathinsert`` (via ``pkg_resources.fixup_namespace_packages``). - - -- `#4993 <https://github.com/pytest-dev/pytest/issues/4993>`_: The stepwise plugin reports status information now. - - -- `#5008 <https://github.com/pytest-dev/pytest/issues/5008>`_: If a ``setup.cfg`` file contains ``[tool:pytest]`` and also the no longer supported ``[pytest]`` section, pytest will use ``[tool:pytest]`` ignoring ``[pytest]``. Previously it would unconditionally error out. - - This makes it simpler for plugins to support old pytest versions. - - - -Bug Fixes ---------- - -- `#1895 <https://github.com/pytest-dev/pytest/issues/1895>`_: Fix bug where fixtures requested dynamically via ``request.getfixturevalue()`` might be teardown - before the requesting fixture. - - -- `#4851 <https://github.com/pytest-dev/pytest/issues/4851>`_: pytester unsets ``PYTEST_ADDOPTS`` now to not use outer options with ``testdir.runpytest()``. - - -- `#4903 <https://github.com/pytest-dev/pytest/issues/4903>`_: Use the correct modified time for years after 2038 in rewritten ``.pyc`` files. - - -- `#4928 <https://github.com/pytest-dev/pytest/issues/4928>`_: Fix line offsets with ``ScopeMismatch`` errors. - - -- `#4957 <https://github.com/pytest-dev/pytest/issues/4957>`_: ``-p no:plugin`` is handled correctly for default (internal) plugins now, e.g. with ``-p no:capture``. - - Previously they were loaded (imported) always, making e.g. the ``capfd`` fixture available. - - -- `#4968 <https://github.com/pytest-dev/pytest/issues/4968>`_: The pdb ``quit`` command is handled properly when used after the ``debug`` command with `pdb++`_. - - .. _pdb++: https://pypi.org/project/pdbpp/ - - -- `#4975 <https://github.com/pytest-dev/pytest/issues/4975>`_: Fix the interpretation of ``-qq`` option where it was being considered as ``-v`` instead. - - -- `#4978 <https://github.com/pytest-dev/pytest/issues/4978>`_: ``outcomes.Exit`` is not swallowed in ``assertrepr_compare`` anymore. - - -- `#4988 <https://github.com/pytest-dev/pytest/issues/4988>`_: Close logging's file handler explicitly when the session finishes. - - -- `#5003 <https://github.com/pytest-dev/pytest/issues/5003>`_: Fix line offset with mark collection error (off by one). - - - -Improved Documentation ----------------------- - -- `#4974 <https://github.com/pytest-dev/pytest/issues/4974>`_: Update docs for ``pytest_cmdline_parse`` hook to note availability liminations - - - -Trivial/Internal Changes ------------------------- - -- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: ``pluggy>=0.9`` is now required. - - -- `#4815 <https://github.com/pytest-dev/pytest/issues/4815>`_: ``funcsigs>=1.0`` is now required for Python 2.7. - - -- `#4829 <https://github.com/pytest-dev/pytest/issues/4829>`_: Some left-over internal code related to ``yield`` tests has been removed. - - -- `#4890 <https://github.com/pytest-dev/pytest/issues/4890>`_: Remove internally unused ``anypython`` fixture from the pytester plugin. - - -- `#4912 <https://github.com/pytest-dev/pytest/issues/4912>`_: Remove deprecated Sphinx directive, ``add_description_unit()``, - pin sphinx-removed-in to >= 0.2.0 to support Sphinx 2.0. - - -- `#4913 <https://github.com/pytest-dev/pytest/issues/4913>`_: Fix pytest tests invocation with custom ``PYTHONPATH``. - - -- `#4965 <https://github.com/pytest-dev/pytest/issues/4965>`_: New ``pytest_report_to_serializable`` and ``pytest_report_from_serializable`` **experimental** hooks. - - These hooks will be used by ``pytest-xdist``, ``pytest-subtests``, and the replacement for - resultlog to serialize and customize reports. - - They are experimental, meaning that their details might change or even be removed - completely in future patch releases without warning. - - Feedback is welcome from plugin authors and users alike. - - -- `#4987 <https://github.com/pytest-dev/pytest/issues/4987>`_: ``Collector.repr_failure`` respects the ``--tb`` option, but only defaults to ``short`` now (with ``auto``). - - -pytest 4.3.1 (2019-03-11) -========================= - -Bug Fixes ---------- - -- `#4810 <https://github.com/pytest-dev/pytest/issues/4810>`_: Logging messages inside ``pytest_runtest_logreport()`` are now properly captured and displayed. - - -- `#4861 <https://github.com/pytest-dev/pytest/issues/4861>`_: Improve validation of contents written to captured output so it behaves the same as when capture is disabled. - - -- `#4898 <https://github.com/pytest-dev/pytest/issues/4898>`_: Fix ``AttributeError: FixtureRequest has no 'confg' attribute`` bug in ``testdir.copy_example``. - - - -Trivial/Internal Changes ------------------------- - -- `#4768 <https://github.com/pytest-dev/pytest/issues/4768>`_: Avoid pkg_resources import at the top-level. - - -pytest 4.3.0 (2019-02-16) -========================= - -Deprecations ------------- - -- `#4724 <https://github.com/pytest-dev/pytest/issues/4724>`_: ``pytest.warns()`` now emits a warning when it receives unknown keyword arguments. - - This will be changed into an error in the future. - - - -Features --------- - -- `#2753 <https://github.com/pytest-dev/pytest/issues/2753>`_: Usage errors from argparse are mapped to pytest's ``UsageError``. - - -- `#3711 <https://github.com/pytest-dev/pytest/issues/3711>`_: Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards. - Add the ``collect_ignore_glob`` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards. - - -- `#4698 <https://github.com/pytest-dev/pytest/issues/4698>`_: The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed. - - In the end it was considered to be more - of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not - install pytest 5.0 on those interpreters. - - -- `#4707 <https://github.com/pytest-dev/pytest/issues/4707>`_: With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks. - - - -Bug Fixes ---------- - -- `#4651 <https://github.com/pytest-dev/pytest/issues/4651>`_: ``--help`` and ``--version`` are handled with ``UsageError``. - - -- `#4782 <https://github.com/pytest-dev/pytest/issues/4782>`_: Fix ``AssertionError`` with collection of broken symlinks with packages. - - -pytest 4.2.1 (2019-02-12) -========================= - -Bug Fixes ---------- - -- `#2895 <https://github.com/pytest-dev/pytest/issues/2895>`_: The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``. - - -- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module. - - -- `#4347 <https://github.com/pytest-dev/pytest/issues/4347>`_: Fix output capturing when using pdb++ with recursive debugging. - - -- `#4592 <https://github.com/pytest-dev/pytest/issues/4592>`_: Fix handling of ``collect_ignore`` via parent ``conftest.py``. - - -- `#4700 <https://github.com/pytest-dev/pytest/issues/4700>`_: Fix regression where ``setUpClass`` would always be called in subclasses even if all tests - were skipped by a ``unittest.skip()`` decorator applied in the subclass. - - -- `#4739 <https://github.com/pytest-dev/pytest/issues/4739>`_: Fix ``parametrize(... ids=<function>)`` when the function returns non-strings. - - -- `#4745 <https://github.com/pytest-dev/pytest/issues/4745>`_: Fix/improve collection of args when passing in ``__init__.py`` and a test file. - - -- `#4770 <https://github.com/pytest-dev/pytest/issues/4770>`_: ``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility. - - -- `#526 <https://github.com/pytest-dev/pytest/issues/526>`_: Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. - - - -Improved Documentation ----------------------- - -- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins. - - -- `#4324 <https://github.com/pytest-dev/pytest/issues/4324>`_: Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises. - - -- `#4709 <https://github.com/pytest-dev/pytest/issues/4709>`_: Document how to customize test failure messages when using - ``pytest.warns``. - - - -Trivial/Internal Changes ------------------------- - -- `#4741 <https://github.com/pytest-dev/pytest/issues/4741>`_: Some verbosity related attributes of the TerminalReporter plugin are now - read only properties. - - -pytest 4.2.0 (2019-01-30) -========================= - -Features --------- - -- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Classic xunit-style <https://docs.pytest.org/en/latest/xunit_setup.html>`__ functions and methods - now obey the scope of *autouse* fixtures. - - This fixes a number of surprising issues like ``setup_method`` being called before session-scoped - autouse fixtures (see `#517 <https://github.com/pytest-dev/pytest/issues/517>`__ for an example). - - -- `#4627 <https://github.com/pytest-dev/pytest/issues/4627>`_: Display a message at the end of the test session when running under Python 2.7 and 3.4 that pytest 5.0 will no longer - support those Python versions. - - -- `#4660 <https://github.com/pytest-dev/pytest/issues/4660>`_: The number of *selected* tests now are also displayed when the ``-k`` or ``-m`` flags are used. - - -- `#4688 <https://github.com/pytest-dev/pytest/issues/4688>`_: ``pytest_report_teststatus`` hook now can also receive a ``config`` parameter. - - -- `#4691 <https://github.com/pytest-dev/pytest/issues/4691>`_: ``pytest_terminal_summary`` hook now can also receive a ``config`` parameter. - - - -Bug Fixes ---------- - -- `#3547 <https://github.com/pytest-dev/pytest/issues/3547>`_: ``--junitxml`` can emit XML compatible with Jenkins xUnit. - ``junit_family`` INI option accepts ``legacy|xunit1``, which produces old style output, and ``xunit2`` that conforms more strictly to https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd - - -- `#4280 <https://github.com/pytest-dev/pytest/issues/4280>`_: Improve quitting from pdb, especially with ``--trace``. - - Using ``q[quit]`` after ``pdb.set_trace()`` will quit pytest also. - - -- `#4402 <https://github.com/pytest-dev/pytest/issues/4402>`_: Warning summary now groups warnings by message instead of by test id. - - This makes the output more compact and better conveys the general idea of how much code is - actually generating warnings, instead of how many tests call that code. - - -- `#4536 <https://github.com/pytest-dev/pytest/issues/4536>`_: ``monkeypatch.delattr`` handles class descriptors like ``staticmethod``/``classmethod``. - - -- `#4649 <https://github.com/pytest-dev/pytest/issues/4649>`_: Restore marks being considered keywords for keyword expressions. - - -- `#4653 <https://github.com/pytest-dev/pytest/issues/4653>`_: ``tmp_path`` fixture and other related ones provides resolved path (a.k.a real path) - - -- `#4667 <https://github.com/pytest-dev/pytest/issues/4667>`_: ``pytest_terminal_summary`` uses result from ``pytest_report_teststatus`` hook, rather than hardcoded strings. - - -- `#4669 <https://github.com/pytest-dev/pytest/issues/4669>`_: Correctly handle ``unittest.SkipTest`` exception containing non-ascii characters on Python 2. - - -- `#4680 <https://github.com/pytest-dev/pytest/issues/4680>`_: Ensure the ``tmpdir`` and the ``tmp_path`` fixtures are the same folder. - - -- `#4681 <https://github.com/pytest-dev/pytest/issues/4681>`_: Ensure ``tmp_path`` is always a real path. - - - -Trivial/Internal Changes ------------------------- - -- `#4643 <https://github.com/pytest-dev/pytest/issues/4643>`_: Use ``a.item()`` instead of the deprecated ``np.asscalar(a)`` in ``pytest.approx``. - - ``np.asscalar`` has been `deprecated <https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations>`__ in ``numpy 1.16.``. - - -- `#4657 <https://github.com/pytest-dev/pytest/issues/4657>`_: Copy saferepr from pylib - - -- `#4668 <https://github.com/pytest-dev/pytest/issues/4668>`_: The verbose word for expected failures in the teststatus report changes from ``xfail`` to ``XFAIL`` to be consistent with other test outcomes. - - -pytest 4.1.1 (2019-01-12) -========================= - -Bug Fixes ---------- - -- `#2256 <https://github.com/pytest-dev/pytest/issues/2256>`_: Show full repr with ``assert a==b`` and ``-vv``. - - -- `#3456 <https://github.com/pytest-dev/pytest/issues/3456>`_: Extend Doctest-modules to ignore mock objects. - - -- `#4617 <https://github.com/pytest-dev/pytest/issues/4617>`_: Fixed ``pytest.warns`` bug when context manager is reused (e.g. multiple parametrization). - - -- `#4631 <https://github.com/pytest-dev/pytest/issues/4631>`_: Don't rewrite assertion when ``__getattr__`` is broken - - - -Improved Documentation ----------------------- - -- `#3375 <https://github.com/pytest-dev/pytest/issues/3375>`_: Document that using ``setup.cfg`` may crash other tools or cause hard to track down problems because it uses a different parser than ``pytest.ini`` or ``tox.ini`` files. - - - -Trivial/Internal Changes ------------------------- - -- `#4602 <https://github.com/pytest-dev/pytest/issues/4602>`_: Uninstall ``hypothesis`` in regen tox env. - - -pytest 4.1.0 (2019-01-05) -========================= - -Removals --------- - -- `#2169 <https://github.com/pytest-dev/pytest/issues/2169>`_: ``pytest.mark.parametrize``: in previous versions, errors raised by id functions were suppressed and changed into warnings. Now the exceptions are propagated, along with a pytest message informing the node, parameter value and index where the exception occurred. - - -- `#3078 <https://github.com/pytest-dev/pytest/issues/3078>`_: Remove legacy internal warnings system: ``config.warn``, ``Node.warn``. The ``pytest_logwarning`` now issues a warning when implemented. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#config-warn-and-node-warn>`__ on information on how to update your code. - - -- `#3079 <https://github.com/pytest-dev/pytest/issues/3079>`_: Removed support for yield tests - they are fundamentally broken because they don't support fixtures properly since collection and test execution were separated. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#yield-tests>`__ on information on how to update your code. - - -- `#3082 <https://github.com/pytest-dev/pytest/issues/3082>`_: Removed support for applying marks directly to values in ``@pytest.mark.parametrize``. Use ``pytest.param`` instead. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#marks-in-pytest-mark-parametrize>`__ on information on how to update your code. - - -- `#3083 <https://github.com/pytest-dev/pytest/issues/3083>`_: Removed ``Metafunc.addcall``. This was the predecessor mechanism to ``@pytest.mark.parametrize``. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#metafunc-addcall>`__ on information on how to update your code. - - -- `#3085 <https://github.com/pytest-dev/pytest/issues/3085>`_: Removed support for passing strings to ``pytest.main``. Now, always pass a list of strings instead. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#passing-command-line-string-to-pytest-main>`__ on information on how to update your code. - - -- `#3086 <https://github.com/pytest-dev/pytest/issues/3086>`_: ``[pytest]`` section in **setup.cfg** files is no longer supported, use ``[tool:pytest]`` instead. ``setup.cfg`` files - are meant for use with ``distutils``, and a section named ``pytest`` has notoriously been a source of conflicts and bugs. - - Note that for **pytest.ini** and **tox.ini** files the section remains ``[pytest]``. - - -- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: Removed the deprecated compat properties for ``node.Class/Function/Module`` - use ``pytest.Class/Function/Module`` now. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#internal-classes-accessed-through-node>`__ on information on how to update your code. - - -- `#4421 <https://github.com/pytest-dev/pytest/issues/4421>`_: Removed the implementation of the ``pytest_namespace`` hook. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-namespace>`__ on information on how to update your code. - - -- `#4489 <https://github.com/pytest-dev/pytest/issues/4489>`_: Removed ``request.cached_setup``. This was the predecessor mechanism to modern fixtures. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#cached-setup>`__ on information on how to update your code. - - -- `#4535 <https://github.com/pytest-dev/pytest/issues/4535>`_: Removed the deprecated ``PyCollector.makeitem`` method. This method was made public by mistake a long time ago. - - -- `#4543 <https://github.com/pytest-dev/pytest/issues/4543>`_: Removed support to define fixtures using the ``pytest_funcarg__`` prefix. Use the ``@pytest.fixture`` decorator instead. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-funcarg-prefix>`__ on information on how to update your code. - - -- `#4545 <https://github.com/pytest-dev/pytest/issues/4545>`_: Calling fixtures directly is now always an error instead of a warning. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly>`__ on information on how to update your code. - - -- `#4546 <https://github.com/pytest-dev/pytest/issues/4546>`_: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check. - - Use ``Node.get_closest_marker(name)`` as a replacement. - - -- `#4547 <https://github.com/pytest-dev/pytest/issues/4547>`_: The deprecated ``record_xml_property`` fixture has been removed, use the more generic ``record_property`` instead. - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#record-xml-property>`__ for more information. - - -- `#4548 <https://github.com/pytest-dev/pytest/issues/4548>`_: An error is now raised if the ``pytest_plugins`` variable is defined in a non-top-level ``conftest.py`` file (i.e., not residing in the ``rootdir``). - - See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`__ for more information. - - -- `#891 <https://github.com/pytest-dev/pytest/issues/891>`_: Remove ``testfunction.markername`` attributes - use ``Node.iter_markers(name=None)`` to iterate them. - - - -Deprecations ------------- - -- `#3050 <https://github.com/pytest-dev/pytest/issues/3050>`_: Deprecated the ``pytest.config`` global. - - See https://docs.pytest.org/en/latest/deprecations.html#pytest-config-global for rationale. - - -- `#3974 <https://github.com/pytest-dev/pytest/issues/3974>`_: Passing the ``message`` parameter of ``pytest.raises`` now issues a ``DeprecationWarning``. - - It is a common mistake to think this parameter will match the exception message, while in fact - it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this - mistake and because it is believed to be little used, pytest is deprecating it without providing - an alternative for the moment. - - If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__. - - -- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Deprecated ``raises(..., 'code(as_a_string)')`` and ``warns(..., 'code(as_a_string)')``. - - See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec for rationale and examples. - - - -Features --------- - -- `#3191 <https://github.com/pytest-dev/pytest/issues/3191>`_: A warning is now issued when assertions are made for ``None``. - - This is a common source of confusion among new users, which write: - - .. code-block:: python - - assert mocked_object.assert_called_with(3, 4, 5, key="value") - - When they should write: - - .. code-block:: python - - mocked_object.assert_called_with(3, 4, 5, key="value") - - Because the ``assert_called_with`` method of mock objects already executes an assertion. - - This warning will not be issued when ``None`` is explicitly checked. An assertion like: - - .. code-block:: python - - assert variable is None - - will not issue the warning. - - -- `#3632 <https://github.com/pytest-dev/pytest/issues/3632>`_: Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs <http://www.attrs.org/en/stable/>`__ or `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ (Python 3.7+, `backported to 3.6 <https://pypi.org/project/dataclasses>`__). - - -- `#4278 <https://github.com/pytest-dev/pytest/issues/4278>`_: ``CACHEDIR.TAG`` files are now created inside cache directories. - - Those files are part of the `Cache Directory Tagging Standard <http://www.bford.info/cachedir/spec.html>`__, and can - be used by backup or synchronization programs to identify pytest's cache directory as such. - - -- `#4292 <https://github.com/pytest-dev/pytest/issues/4292>`_: ``pytest.outcomes.Exit`` is derived from ``SystemExit`` instead of ``KeyboardInterrupt``. This allows us to better handle ``pdb`` exiting. - - -- `#4371 <https://github.com/pytest-dev/pytest/issues/4371>`_: Updated the ``--collect-only`` option to display test descriptions when ran using ``--verbose``. - - -- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Restructured ``ExceptionInfo`` object construction and ensure incomplete instances have a ``repr``/``str``. - - -- `#4416 <https://github.com/pytest-dev/pytest/issues/4416>`_: pdb: added support for keyword arguments with ``pdb.set_trace``. - - It handles ``header`` similar to Python 3.7 does it, and forwards any - other keyword arguments to the ``Pdb`` constructor. - - This allows for ``__import__("pdb").set_trace(skip=["foo.*"])``. - - -- `#4483 <https://github.com/pytest-dev/pytest/issues/4483>`_: Added ini parameter ``junit_duration_report`` to optionally report test call durations, excluding setup and teardown times. - - The JUnit XML specification and the default pytest behavior is to include setup and teardown times in the test duration - report. You can include just the call durations instead (excluding setup and teardown) by adding this to your ``pytest.ini`` file: - - .. code-block:: ini - - [pytest] - junit_duration_report = call - - -- `#4532 <https://github.com/pytest-dev/pytest/issues/4532>`_: ``-ra`` now will show errors and failures last, instead of as the first items in the summary. - - This makes it easier to obtain a list of errors and failures to run tests selectively. - - -- `#4599 <https://github.com/pytest-dev/pytest/issues/4599>`_: ``pytest.importorskip`` now supports a ``reason`` parameter, which will be shown when the - requested module cannot be imported. - - - -Bug Fixes ---------- - -- `#3532 <https://github.com/pytest-dev/pytest/issues/3532>`_: ``-p`` now accepts its argument without a space between the value, for example ``-pmyplugin``. - - -- `#4327 <https://github.com/pytest-dev/pytest/issues/4327>`_: ``approx`` again works with more generic containers, more precisely instances of ``Iterable`` and ``Sized`` instead of more restrictive ``Sequence``. - - -- `#4397 <https://github.com/pytest-dev/pytest/issues/4397>`_: Ensure that node ids are printable. - - -- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fixed ``raises(..., 'code(string)')`` frame filename. - - -- `#4458 <https://github.com/pytest-dev/pytest/issues/4458>`_: Display actual test ids in ``--collect-only``. - - - -Improved Documentation ----------------------- - -- `#4557 <https://github.com/pytest-dev/pytest/issues/4557>`_: Markers example documentation page updated to support latest pytest version. - - -- `#4558 <https://github.com/pytest-dev/pytest/issues/4558>`_: Update cache documentation example to correctly show cache hit and miss. - - -- `#4580 <https://github.com/pytest-dev/pytest/issues/4580>`_: Improved detailed summary report documentation. - - - -Trivial/Internal Changes ------------------------- - -- `#4447 <https://github.com/pytest-dev/pytest/issues/4447>`_: Changed the deprecation type of ``--result-log`` to ``PytestDeprecationWarning``. - - It was decided to remove this feature at the next major revision. - - -pytest 4.0.2 (2018-12-13) -========================= - -Bug Fixes ---------- - -- `#4265 <https://github.com/pytest-dev/pytest/issues/4265>`_: Validate arguments from the ``PYTEST_ADDOPTS`` environment variable and the ``addopts`` ini option separately. - - -- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fix ``raises(..., 'code(string)')`` frame filename. - - -- `#4500 <https://github.com/pytest-dev/pytest/issues/4500>`_: When a fixture yields and a log call is made after the test runs, and, if the test is interrupted, capture attributes are ``None``. - - -- `#4538 <https://github.com/pytest-dev/pytest/issues/4538>`_: Raise ``TypeError`` for ``with raises(..., match=<non-None falsey value>)``. - - - -Improved Documentation ----------------------- - -- `#1495 <https://github.com/pytest-dev/pytest/issues/1495>`_: Document common doctest fixture directory tree structure pitfalls - - -pytest 4.0.1 (2018-11-23) -========================= - -Bug Fixes ---------- - -- `#3952 <https://github.com/pytest-dev/pytest/issues/3952>`_: Display warnings before "short test summary info" again, but still later warnings in the end. - - -- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Handle uninitialized exceptioninfo in repr/str. - - -- `#4393 <https://github.com/pytest-dev/pytest/issues/4393>`_: Do not create ``.gitignore``/``README.md`` files in existing cache directories. - - -- `#4400 <https://github.com/pytest-dev/pytest/issues/4400>`_: Rearrange warning handling for the yield test errors so the opt-out in 4.0.x correctly works. - - -- `#4405 <https://github.com/pytest-dev/pytest/issues/4405>`_: Fix collection of testpaths with ``--pyargs``. - - -- `#4412 <https://github.com/pytest-dev/pytest/issues/4412>`_: Fix assertion rewriting involving ``Starred`` + side-effects. - - -- `#4425 <https://github.com/pytest-dev/pytest/issues/4425>`_: Ensure we resolve the absolute path when the given ``--basetemp`` is a relative path. - - - -Trivial/Internal Changes ------------------------- - -- `#4315 <https://github.com/pytest-dev/pytest/issues/4315>`_: Use ``pkg_resources.parse_version`` instead of ``LooseVersion`` in minversion check. - - -- `#4440 <https://github.com/pytest-dev/pytest/issues/4440>`_: Adjust the stack level of some internal pytest warnings. - - -pytest 4.0.0 (2018-11-13) -========================= - -Removals --------- - -- `#3737 <https://github.com/pytest-dev/pytest/issues/3737>`_: **RemovedInPytest4Warnings are now errors by default.** - - Following our plan to remove deprecated features with as little disruption as - possible, all warnings of type ``RemovedInPytest4Warnings`` now generate errors - instead of warning messages. - - **The affected features will be effectively removed in pytest 4.1**, so please consult the - `Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ - section in the docs for directions on how to update existing code. - - In the pytest ``4.0.X`` series, it is possible to change the errors back into warnings as a stop - gap measure by adding this to your ``pytest.ini`` file: - - .. code-block:: ini - - [pytest] - filterwarnings = - ignore::pytest.RemovedInPytest4Warning - - But this will stop working when pytest ``4.1`` is released. - - **If you have concerns** about the removal of a specific feature, please add a - comment to `#4348 <https://github.com/pytest-dev/pytest/issues/4348>`__. - - -- `#4358 <https://github.com/pytest-dev/pytest/issues/4358>`_: Remove the ``::()`` notation to denote a test class instance in node ids. - - Previously, node ids that contain test instances would use ``::()`` to denote the instance like this:: - - test_foo.py::Test::()::test_bar - - The extra ``::()`` was puzzling to most users and has been removed, so that the test id becomes now:: - - test_foo.py::Test::test_bar - - This change could not accompany a deprecation period as is usual when user-facing functionality changes because - it was not really possible to detect when the functionality was being used explicitly. - - The extra ``::()`` might have been removed in some places internally already, - which then led to confusion in places where it was expected, e.g. with - ``--deselect`` (`#4127 <https://github.com/pytest-dev/pytest/issues/4127>`_). - - Test class instances are also not listed with ``--collect-only`` anymore. - - - -Features --------- - -- `#4270 <https://github.com/pytest-dev/pytest/issues/4270>`_: The ``cache_dir`` option uses ``$TOX_ENV_DIR`` as prefix (if set in the environment). - - This uses a different cache per tox environment by default. - - - -Bug Fixes ---------- - -- `#3554 <https://github.com/pytest-dev/pytest/issues/3554>`_: Fix ``CallInfo.__repr__`` for when the call is not finished yet. - - -pytest 3.10.1 (2018-11-11) -========================== - -Bug Fixes ---------- - -- `#4287 <https://github.com/pytest-dev/pytest/issues/4287>`_: Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``. - - -- `#4304 <https://github.com/pytest-dev/pytest/issues/4304>`_: Block the ``stepwise`` plugin if ``cacheprovider`` is also blocked, as one depends on the other. - - -- `#4306 <https://github.com/pytest-dev/pytest/issues/4306>`_: Parse ``minversion`` as an actual version and not as dot-separated strings. - - -- `#4310 <https://github.com/pytest-dev/pytest/issues/4310>`_: Fix duplicate collection due to multiple args matching the same packages. - - -- `#4321 <https://github.com/pytest-dev/pytest/issues/4321>`_: Fix ``item.nodeid`` with resolved symlinks. - - -- `#4325 <https://github.com/pytest-dev/pytest/issues/4325>`_: Fix collection of direct symlinked files, where the target does not match ``python_files``. - - -- `#4329 <https://github.com/pytest-dev/pytest/issues/4329>`_: Fix TypeError in report_collect with _collect_report_last_write. - - - -Trivial/Internal Changes ------------------------- - -- `#4305 <https://github.com/pytest-dev/pytest/issues/4305>`_: Replace byte/unicode helpers in test_capture with python level syntax. - - -pytest 3.10.0 (2018-11-03) -========================== - -Features --------- - -- `#2619 <https://github.com/pytest-dev/pytest/issues/2619>`_: Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``. - - This also adds a new ``pytest_leave_pdb`` hook, and passes in ``pdb`` to the - existing ``pytest_enter_pdb`` hook. - - -- `#4147 <https://github.com/pytest-dev/pytest/issues/4147>`_: Add ``--sw``, ``--stepwise`` as an alternative to ``--lf -x`` for stopping at the first failure, but starting the next test invocation from that test. See `the documentation <https://docs.pytest.org/en/latest/cache.html#stepwise>`__ for more info. - - -- `#4188 <https://github.com/pytest-dev/pytest/issues/4188>`_: Make ``--color`` emit colorful dots when not running in verbose mode. Earlier, it would only colorize the test-by-test output if ``--verbose`` was also passed. - - -- `#4225 <https://github.com/pytest-dev/pytest/issues/4225>`_: Improve performance with collection reporting in non-quiet mode with terminals. - - The "collecting …" message is only printed/updated every 0.5s. - - - -Bug Fixes ---------- - -- `#2701 <https://github.com/pytest-dev/pytest/issues/2701>`_: Fix false ``RemovedInPytest4Warning: usage of Session... is deprecated, please use pytest`` warnings. - - -- `#4046 <https://github.com/pytest-dev/pytest/issues/4046>`_: Fix problems with running tests in package ``__init__.py`` files. - - -- `#4260 <https://github.com/pytest-dev/pytest/issues/4260>`_: Swallow warnings during anonymous compilation of source. - - -- `#4262 <https://github.com/pytest-dev/pytest/issues/4262>`_: Fix access denied error when deleting stale directories created by ``tmpdir`` / ``tmp_path``. - - -- `#611 <https://github.com/pytest-dev/pytest/issues/611>`_: Naming a fixture ``request`` will now raise a warning: the ``request`` fixture is internal and - should not be overwritten as it will lead to internal errors. - -- `#4266 <https://github.com/pytest-dev/pytest/issues/4266>`_: Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class. - - - -Improved Documentation ----------------------- - -- `#4255 <https://github.com/pytest-dev/pytest/issues/4255>`_: Added missing documentation about the fact that module names passed to filter warnings are not regex-escaped. - - - -Trivial/Internal Changes ------------------------- - -- `#4272 <https://github.com/pytest-dev/pytest/issues/4272>`_: Display cachedir also in non-verbose mode if non-default. - - -- `#4277 <https://github.com/pytest-dev/pytest/issues/4277>`_: pdb: improve message about output capturing with ``set_trace``. - - Do not display "IO-capturing turned off/on" when ``-s`` is used to avoid - confusion. - - -- `#4279 <https://github.com/pytest-dev/pytest/issues/4279>`_: Improve message and stack level of warnings issued by ``monkeypatch.setenv`` when the value of the environment variable is not a ``str``. - - -pytest 3.9.3 (2018-10-27) -========================= - -Bug Fixes ---------- - -- `#4174 <https://github.com/pytest-dev/pytest/issues/4174>`_: Fix "ValueError: Plugin already registered" with conftest plugins via symlink. - - -- `#4181 <https://github.com/pytest-dev/pytest/issues/4181>`_: Handle race condition between creation and deletion of temporary folders. - - -- `#4221 <https://github.com/pytest-dev/pytest/issues/4221>`_: Fix bug where the warning summary at the end of the test session was not showing the test where the warning was originated. - - -- `#4243 <https://github.com/pytest-dev/pytest/issues/4243>`_: Fix regression when ``stacklevel`` for warnings was passed as positional argument on python2. - - - -Improved Documentation ----------------------- - -- `#3851 <https://github.com/pytest-dev/pytest/issues/3851>`_: Add reference to ``empty_parameter_set_mark`` ini option in documentation of ``@pytest.mark.parametrize`` - - - -Trivial/Internal Changes ------------------------- - -- `#4028 <https://github.com/pytest-dev/pytest/issues/4028>`_: Revert patching of ``sys.breakpointhook`` since it appears to do nothing. - - -- `#4233 <https://github.com/pytest-dev/pytest/issues/4233>`_: Apply an import sorter (``reorder-python-imports``) to the codebase. - - -- `#4248 <https://github.com/pytest-dev/pytest/issues/4248>`_: Remove use of unnecessary compat shim, six.binary_type - - -pytest 3.9.2 (2018-10-22) -========================= - -Bug Fixes ---------- - -- `#2909 <https://github.com/pytest-dev/pytest/issues/2909>`_: Improve error message when a recursive dependency between fixtures is detected. - - -- `#3340 <https://github.com/pytest-dev/pytest/issues/3340>`_: Fix logging messages not shown in hooks ``pytest_sessionstart()`` and ``pytest_sessionfinish()``. - - -- `#3533 <https://github.com/pytest-dev/pytest/issues/3533>`_: Fix unescaped XML raw objects in JUnit report for skipped tests - - -- `#3691 <https://github.com/pytest-dev/pytest/issues/3691>`_: Python 2: safely format warning message about passing unicode strings to ``warnings.warn``, which may cause - surprising ``MemoryError`` exception when monkey patching ``warnings.warn`` itself. - - -- `#4026 <https://github.com/pytest-dev/pytest/issues/4026>`_: Improve error message when it is not possible to determine a function's signature. - - -- `#4177 <https://github.com/pytest-dev/pytest/issues/4177>`_: Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg`` - - -- `#4179 <https://github.com/pytest-dev/pytest/issues/4179>`_: Restore the tmpdir behaviour of symlinking the current test run. - - -- `#4192 <https://github.com/pytest-dev/pytest/issues/4192>`_: Fix filename reported by ``warnings.warn`` when using ``recwarn`` under python2. - - -pytest 3.9.1 (2018-10-16) -========================= - -Features --------- - -- `#4159 <https://github.com/pytest-dev/pytest/issues/4159>`_: For test-suites containing test classes, the information about the subclassed - module is now output only if a higher verbosity level is specified (at least - "-vv"). - - -pytest 3.9.0 (2018-10-15 - not published due to a release automation bug) -========================================================================= - -Deprecations ------------- - -- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings. - - * Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now - users will this warning:: - - usage of Function.Module is deprecated, please use pytest.Module instead - - Users should just ``import pytest`` and access those objects using the ``pytest`` module. - - * ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can - consult `funcarg comparison section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_. - - * Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` - subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during - collection. - - This issue should affect only advanced plugins who create new collection types, so if you see this warning - message please contact the authors so they can change the code. - - * The warning that produces the message below has changed to ``RemovedInPytest4Warning``:: - - getfuncargvalue is deprecated, use getfixturevalue - - -- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Add a Deprecation warning for pytest.ensuretemp as it was deprecated since a while. - - - -Features --------- - -- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: Improve usage errors messages by hiding internal details which can be distracting and noisy. - - This has the side effect that some error conditions that previously raised generic errors (such as - ``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions. - - -- `#3332 <https://github.com/pytest-dev/pytest/issues/3332>`_: Improve the error displayed when a ``conftest.py`` file could not be imported. - - In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr`` - to show or hide chained tracebacks in Python 3 (defaults to ``True``). - - -- `#3849 <https://github.com/pytest-dev/pytest/issues/3849>`_: Add ``empty_parameter_set_mark=fail_at_collect`` ini option for raising an exception when parametrize collects an empty set. - - -- `#3964 <https://github.com/pytest-dev/pytest/issues/3964>`_: Log messages generated in the collection phase are shown when - live-logging is enabled and/or when they are logged to a file. - - -- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object. - - -- `#4013 <https://github.com/pytest-dev/pytest/issues/4013>`_: Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version - any customization would override pytest's filters and deprecation warnings would fall back to being hidden by default. - - -- `#4073 <https://github.com/pytest-dev/pytest/issues/4073>`_: Allow specification of timeout for ``Testdir.runpytest_subprocess()`` and ``Testdir.run()``. - - -- `#4098 <https://github.com/pytest-dev/pytest/issues/4098>`_: Add returncode argument to pytest.exit() to exit pytest with a specific return code. - - -- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument. - - This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead - of ``AssertionError``. - - -- `#4149 <https://github.com/pytest-dev/pytest/issues/4149>`_: Require setuptools>=30.3 and move most of the metadata to ``setup.cfg``. - - - -Bug Fixes ---------- - -- `#2535 <https://github.com/pytest-dev/pytest/issues/2535>`_: Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture. - - -- `#3057 <https://github.com/pytest-dev/pytest/issues/3057>`_: ``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``. - - -- `#3946 <https://github.com/pytest-dev/pytest/issues/3946>`_: Warning filters passed as command line options using ``-W`` now take precedence over filters defined in ``ini`` - configuration files. - - -- `#4066 <https://github.com/pytest-dev/pytest/issues/4066>`_: Fix source reindenting by using ``textwrap.dedent`` directly. - - -- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: ``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised. - - -- `#4108 <https://github.com/pytest-dev/pytest/issues/4108>`_: Resolve symbolic links for args. - - This fixes running ``pytest tests/test_foo.py::test_bar``, where ``tests`` - is a symlink to ``project/app/tests``: - previously ``project/app/conftest.py`` would be ignored for fixtures then. - - -- `#4132 <https://github.com/pytest-dev/pytest/issues/4132>`_: Fix duplicate printing of internal errors when using ``--pdb``. - - -- `#4135 <https://github.com/pytest-dev/pytest/issues/4135>`_: pathlib based tmpdir cleanup now correctly handles symlinks in the folder. - - -- `#4152 <https://github.com/pytest-dev/pytest/issues/4152>`_: Display the filename when encountering ``SyntaxWarning``. - - - -Improved Documentation ----------------------- - -- `#3713 <https://github.com/pytest-dev/pytest/issues/3713>`_: Update usefixtures documentation to clarify that it can't be used with fixture functions. - - -- `#4058 <https://github.com/pytest-dev/pytest/issues/4058>`_: Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for. - - -- `#4064 <https://github.com/pytest-dev/pytest/issues/4064>`_: According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest. - - -- `#4151 <https://github.com/pytest-dev/pytest/issues/4151>`_: Add tempir testing example to CONTRIBUTING.rst guide - - - -Trivial/Internal Changes ------------------------- - -- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: The internal ``MarkerError`` exception has been removed. - - -- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Port the implementation of tmpdir to pathlib. - - -- `#4063 <https://github.com/pytest-dev/pytest/issues/4063>`_: Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line. - - -- `#4093 <https://github.com/pytest-dev/pytest/issues/4093>`_: Fixed formatting of string literals in internal tests. - - -pytest 3.8.2 (2018-10-02) -========================= - -Deprecations and Removals -------------------------- - -- `#4036 <https://github.com/pytest-dev/pytest/issues/4036>`_: The ``item`` parameter of ``pytest_warning_captured`` hook is now documented as deprecated. We realized only after - the ``3.8`` release that this parameter is incompatible with ``pytest-xdist``. - - Our policy is to not deprecate features during bugfix releases, but in this case we believe it makes sense as we are - only documenting it as deprecated, without issuing warnings which might potentially break test suites. This will get - the word out that hook implementers should not use this parameter at all. - - In a future release ``item`` will always be ``None`` and will emit a proper warning when a hook implementation - makes use of it. - - - -Bug Fixes ---------- - -- `#3539 <https://github.com/pytest-dev/pytest/issues/3539>`_: Fix reload on assertion rewritten modules. - - -- `#4034 <https://github.com/pytest-dev/pytest/issues/4034>`_: The ``.user_properties`` attribute of ``TestReport`` objects is a list - of (name, value) tuples, but could sometimes be instantiated as a tuple - of tuples. It is now always a list. - - -- `#4039 <https://github.com/pytest-dev/pytest/issues/4039>`_: No longer issue warnings about using ``pytest_plugins`` in non-top-level directories when using ``--pyargs``: the - current ``--pyargs`` mechanism is not reliable and might give false negatives. - - -- `#4040 <https://github.com/pytest-dev/pytest/issues/4040>`_: Exclude empty reports for passed tests when ``-rP`` option is used. - - -- `#4051 <https://github.com/pytest-dev/pytest/issues/4051>`_: Improve error message when an invalid Python expression is passed to the ``-m`` option. - - -- `#4056 <https://github.com/pytest-dev/pytest/issues/4056>`_: ``MonkeyPatch.setenv`` and ``MonkeyPatch.delenv`` issue a warning if the environment variable name is not ``str`` on Python 2. - - In Python 2, adding ``unicode`` keys to ``os.environ`` causes problems with ``subprocess`` (and possible other modules), - making this a subtle bug specially susceptible when used with ``from __future__ import unicode_literals``. - - - -Improved Documentation ----------------------- - -- `#3928 <https://github.com/pytest-dev/pytest/issues/3928>`_: Add possible values for fixture scope to docs. - - -pytest 3.8.1 (2018-09-22) -========================= - -Bug Fixes ---------- - -- `#3286 <https://github.com/pytest-dev/pytest/issues/3286>`_: ``.pytest_cache`` directory is now automatically ignored by Git. Users who would like to contribute a solution for other SCMs please consult/comment on this issue. - - -- `#3749 <https://github.com/pytest-dev/pytest/issues/3749>`_: Fix the following error during collection of tests inside packages:: - - TypeError: object of type 'Package' has no len() - - -- `#3941 <https://github.com/pytest-dev/pytest/issues/3941>`_: Fix bug where indirect parametrization would consider the scope of all fixtures used by the test function to determine the parametrization scope, and not only the scope of the fixtures being parametrized. - - -- `#3973 <https://github.com/pytest-dev/pytest/issues/3973>`_: Fix crash of the assertion rewriter if a test changed the current working directory without restoring it afterwards. - - -- `#3998 <https://github.com/pytest-dev/pytest/issues/3998>`_: Fix issue that prevented some caplog properties (for example ``record_tuples``) from being available when entering the debugger with ``--pdb``. - - -- `#3999 <https://github.com/pytest-dev/pytest/issues/3999>`_: Fix ``UnicodeDecodeError`` in python2.x when a class returns a non-ascii binary ``__repr__`` in an assertion which also contains non-ascii text. - - - -Improved Documentation ----------------------- - -- `#3996 <https://github.com/pytest-dev/pytest/issues/3996>`_: New `Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`_ page shows all currently - deprecated features, the rationale to do so, and alternatives to update your code. It also list features removed - from pytest in past major releases to help those with ancient pytest versions to upgrade. - - - -Trivial/Internal Changes ------------------------- - -- `#3955 <https://github.com/pytest-dev/pytest/issues/3955>`_: Improve pre-commit detection for changelog filenames - - -- `#3975 <https://github.com/pytest-dev/pytest/issues/3975>`_: Remove legacy code around im_func as that was python2 only - - -pytest 3.8.0 (2018-09-05) -========================= - -Deprecations and Removals -------------------------- - -- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: ``Config.warn`` and ``Node.warn`` have been - deprecated, see `<https://docs.pytest.org/en/latest/deprecations.html#config-warn-and-node-warn>`_ for rationale and - examples. - -- `#3936 <https://github.com/pytest-dev/pytest/issues/3936>`_: ``@pytest.mark.filterwarnings`` second parameter is no longer regex-escaped, - making it possible to actually use regular expressions to check the warning message. - - **Note**: regex-escaping the match string was an implementation oversight that might break test suites which depend - on the old behavior. - - - -Features --------- - -- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: Internal pytest warnings are now issued using the standard ``warnings`` module, making it possible to use - the standard warnings filters to manage those warnings. This introduces ``PytestWarning``, - ``PytestDeprecationWarning`` and ``RemovedInPytest4Warning`` warning types as part of the public API. - - Consult `the documentation <https://docs.pytest.org/en/latest/warnings.html#internal-pytest-warnings>`__ for more info. - - -- `#2908 <https://github.com/pytest-dev/pytest/issues/2908>`_: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is - configured. This makes pytest more compliant with - `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_. See - `the docs <https://docs.pytest.org/en/latest/warnings.html#deprecationwarning-and-pendingdeprecationwarning>`_ for - more info. - - -- `#3251 <https://github.com/pytest-dev/pytest/issues/3251>`_: Warnings are now captured and displayed during test collection. - - -- `#3784 <https://github.com/pytest-dev/pytest/issues/3784>`_: ``PYTEST_DISABLE_PLUGIN_AUTOLOAD`` environment variable disables plugin auto-loading when set. - - -- `#3829 <https://github.com/pytest-dev/pytest/issues/3829>`_: Added the ``count`` option to ``console_output_style`` to enable displaying the progress as a count instead of a percentage. - - -- `#3837 <https://github.com/pytest-dev/pytest/issues/3837>`_: Added support for 'xfailed' and 'xpassed' outcomes to the ``pytester.RunResult.assert_outcomes`` signature. - - - -Bug Fixes ---------- - -- `#3911 <https://github.com/pytest-dev/pytest/issues/3911>`_: Terminal writer now takes into account unicode character width when writing out progress. - - -- `#3913 <https://github.com/pytest-dev/pytest/issues/3913>`_: Pytest now returns with correct exit code (EXIT_USAGEERROR, 4) when called with unknown arguments. - - -- `#3918 <https://github.com/pytest-dev/pytest/issues/3918>`_: Improve performance of assertion rewriting. - - - -Improved Documentation ----------------------- - -- `#3566 <https://github.com/pytest-dev/pytest/issues/3566>`_: Added a blurb in usage.rst for the usage of -r flag which is used to show an extra test summary info. - - -- `#3907 <https://github.com/pytest-dev/pytest/issues/3907>`_: Corrected type of the exceptions collection passed to ``xfail``: ``raises`` argument accepts a ``tuple`` instead of ``list``. - - - -Trivial/Internal Changes ------------------------- - -- `#3853 <https://github.com/pytest-dev/pytest/issues/3853>`_: Removed ``"run all (no recorded failures)"`` message printed with ``--failed-first`` and ``--last-failed`` when there are no failed tests. - - -pytest 3.7.4 (2018-08-29) -========================= - -Bug Fixes ---------- - -- `#3506 <https://github.com/pytest-dev/pytest/issues/3506>`_: Fix possible infinite recursion when writing ``.pyc`` files. - - -- `#3853 <https://github.com/pytest-dev/pytest/issues/3853>`_: Cache plugin now obeys the ``-q`` flag when ``--last-failed`` and ``--failed-first`` flags are used. - - -- `#3883 <https://github.com/pytest-dev/pytest/issues/3883>`_: Fix bad console output when using ``console_output_style=classic``. - - -- `#3888 <https://github.com/pytest-dev/pytest/issues/3888>`_: Fix macOS specific code using ``capturemanager`` plugin in doctests. - - - -Improved Documentation ----------------------- - -- `#3902 <https://github.com/pytest-dev/pytest/issues/3902>`_: Fix pytest.org links - - -pytest 3.7.3 (2018-08-26) -========================= - -Bug Fixes ---------- - -- `#3033 <https://github.com/pytest-dev/pytest/issues/3033>`_: Fixtures during teardown can again use ``capsys`` and ``capfd`` to inspect output captured during tests. - - -- `#3773 <https://github.com/pytest-dev/pytest/issues/3773>`_: Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option. - - -- `#3796 <https://github.com/pytest-dev/pytest/issues/3796>`_: Fix issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer - package. - - -- `#3816 <https://github.com/pytest-dev/pytest/issues/3816>`_: Fix bug where ``--show-capture=no`` option would still show logs printed during fixture teardown. - - -- `#3819 <https://github.com/pytest-dev/pytest/issues/3819>`_: Fix ``stdout/stderr`` not getting captured when real-time cli logging is active. - - -- `#3843 <https://github.com/pytest-dev/pytest/issues/3843>`_: Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-modules``. - - -- `#3848 <https://github.com/pytest-dev/pytest/issues/3848>`_: Fix bugs where unicode arguments could not be passed to ``testdir.runpytest`` on Python 2. - - -- `#3854 <https://github.com/pytest-dev/pytest/issues/3854>`_: Fix double collection of tests within packages when the filename starts with a capital letter. - - - -Improved Documentation ----------------------- - -- `#3824 <https://github.com/pytest-dev/pytest/issues/3824>`_: Added example for multiple glob pattern matches in ``python_files``. - - -- `#3833 <https://github.com/pytest-dev/pytest/issues/3833>`_: Added missing docs for ``pytester.Testdir``. - - -- `#3870 <https://github.com/pytest-dev/pytest/issues/3870>`_: Correct documentation for setuptools integration. - - - -Trivial/Internal Changes ------------------------- - -- `#3826 <https://github.com/pytest-dev/pytest/issues/3826>`_: Replace broken type annotations with type comments. - - -- `#3845 <https://github.com/pytest-dev/pytest/issues/3845>`_: Remove a reference to issue `#568 <https://github.com/pytest-dev/pytest/issues/568>`_ from the documentation, which has since been - fixed. - - -pytest 3.7.2 (2018-08-16) -========================= - -Bug Fixes ---------- - -- `#3671 <https://github.com/pytest-dev/pytest/issues/3671>`_: Fix ``filterwarnings`` not being registered as a builtin mark. - - -- `#3768 <https://github.com/pytest-dev/pytest/issues/3768>`_, `#3789 <https://github.com/pytest-dev/pytest/issues/3789>`_: Fix test collection from packages mixed with normal directories. - - -- `#3771 <https://github.com/pytest-dev/pytest/issues/3771>`_: Fix infinite recursion during collection if a ``pytest_ignore_collect`` hook returns ``False`` instead of ``None``. - - -- `#3774 <https://github.com/pytest-dev/pytest/issues/3774>`_: Fix bug where decorated fixtures would lose functionality (for example ``@mock.patch``). - - -- `#3775 <https://github.com/pytest-dev/pytest/issues/3775>`_: Fix bug where importing modules or other objects with prefix ``pytest_`` prefix would raise a ``PluginValidationError``. - - -- `#3788 <https://github.com/pytest-dev/pytest/issues/3788>`_: Fix ``AttributeError`` during teardown of ``TestCase`` subclasses which raise an exception during ``__init__``. - - -- `#3804 <https://github.com/pytest-dev/pytest/issues/3804>`_: Fix traceback reporting for exceptions with ``__cause__`` cycles. - - - -Improved Documentation ----------------------- - -- `#3746 <https://github.com/pytest-dev/pytest/issues/3746>`_: Add documentation for ``metafunc.config`` that had been mistakenly hidden. - - -pytest 3.7.1 (2018-08-02) -========================= - -Bug Fixes ---------- - -- `#3473 <https://github.com/pytest-dev/pytest/issues/3473>`_: Raise immediately if ``approx()`` is given an expected value of a type it doesn't understand (e.g. strings, nested dicts, etc.). - - -- `#3712 <https://github.com/pytest-dev/pytest/issues/3712>`_: Correctly represent the dimensions of a numpy array when calling ``repr()`` on ``approx()``. - -- `#3742 <https://github.com/pytest-dev/pytest/issues/3742>`_: Fix incompatibility with third party plugins during collection, which produced the error ``object has no attribute '_collectfile'``. - -- `#3745 <https://github.com/pytest-dev/pytest/issues/3745>`_: Display the absolute path if ``cache_dir`` is not relative to the ``rootdir`` instead of failing. - - -- `#3747 <https://github.com/pytest-dev/pytest/issues/3747>`_: Fix compatibility problem with plugins and the warning code issued by fixture functions when they are called directly. - - -- `#3748 <https://github.com/pytest-dev/pytest/issues/3748>`_: Fix infinite recursion in ``pytest.approx`` with arrays in ``numpy<1.13``. - - -- `#3757 <https://github.com/pytest-dev/pytest/issues/3757>`_: Pin pathlib2 to ``>=2.2.0`` as we require ``__fspath__`` support. - - -- `#3763 <https://github.com/pytest-dev/pytest/issues/3763>`_: Fix ``TypeError`` when the assertion message is ``bytes`` in python 3. - - -pytest 3.7.0 (2018-07-30) -========================= - -Deprecations and Removals -------------------------- - -- `#2639 <https://github.com/pytest-dev/pytest/issues/2639>`_: ``pytest_namespace`` has been `deprecated <https://docs.pytest.org/en/latest/deprecations.html#pytest-namespace>`_. - - -- `#3661 <https://github.com/pytest-dev/pytest/issues/3661>`_: Calling a fixture function directly, as opposed to request them in a test function, now issues a ``RemovedInPytest4Warning``. See `the documentation for rationale and examples <https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly>`_. - - - -Features --------- - -- `#2283 <https://github.com/pytest-dev/pytest/issues/2283>`_: New ``package`` fixture scope: fixtures are finalized when the last test of a *package* finishes. This feature is considered **experimental**, so use it sparingly. - - -- `#3576 <https://github.com/pytest-dev/pytest/issues/3576>`_: ``Node.add_marker`` now supports an ``append=True/False`` parameter to determine whether the mark comes last (default) or first. - - -- `#3579 <https://github.com/pytest-dev/pytest/issues/3579>`_: Fixture ``caplog`` now has a ``messages`` property, providing convenient access to the format-interpolated log messages without the extra data provided by the formatter/handler. - - -- `#3610 <https://github.com/pytest-dev/pytest/issues/3610>`_: New ``--trace`` option to enter the debugger at the start of a test. - - -- `#3623 <https://github.com/pytest-dev/pytest/issues/3623>`_: Introduce ``pytester.copy_example`` as helper to do acceptance tests against examples from the project. - - - -Bug Fixes ---------- - -- `#2220 <https://github.com/pytest-dev/pytest/issues/2220>`_: Fix a bug where fixtures overridden by direct parameters (for example parametrization) were being instantiated even if they were not being used by a test. - - -- `#3695 <https://github.com/pytest-dev/pytest/issues/3695>`_: Fix ``ApproxNumpy`` initialisation argument mixup, ``abs`` and ``rel`` tolerances were flipped causing strange comparison results. - Add tests to check ``abs`` and ``rel`` tolerances for ``np.array`` and test for expecting ``nan`` with ``np.array()`` - - -- `#980 <https://github.com/pytest-dev/pytest/issues/980>`_: Fix truncated locals output in verbose mode. - - - -Improved Documentation ----------------------- - -- `#3295 <https://github.com/pytest-dev/pytest/issues/3295>`_: Correct the usage documentation of ``--last-failed-no-failures`` by adding the missing ``--last-failed`` argument in the presented examples, because they are misleading and lead to think that the missing argument is not needed. - - - -Trivial/Internal Changes ------------------------- - -- `#3519 <https://github.com/pytest-dev/pytest/issues/3519>`_: Now a ``README.md`` file is created in ``.pytest_cache`` to make it clear why the directory exists. - - -pytest 3.6.4 (2018-07-28) -========================= - -Bug Fixes ---------- - -- Invoke pytest using ``-mpytest`` so ``sys.path`` does not get polluted by packages installed in ``site-packages``. (`#742 <https://github.com/pytest-dev/pytest/issues/742>`_) - - -Improved Documentation ----------------------- - -- Use ``smtp_connection`` instead of ``smtp`` in fixtures documentation to avoid possible confusion. (`#3592 <https://github.com/pytest-dev/pytest/issues/3592>`_) - - -Trivial/Internal Changes ------------------------- - -- Remove obsolete ``__future__`` imports. (`#2319 <https://github.com/pytest-dev/pytest/issues/2319>`_) - -- Add CITATION to provide information on how to formally cite pytest. (`#3402 <https://github.com/pytest-dev/pytest/issues/3402>`_) - -- Replace broken type annotations with type comments. (`#3635 <https://github.com/pytest-dev/pytest/issues/3635>`_) - -- Pin ``pluggy`` to ``<0.8``. (`#3727 <https://github.com/pytest-dev/pytest/issues/3727>`_) - - -pytest 3.6.3 (2018-07-04) -========================= - -Bug Fixes ---------- - -- Fix ``ImportWarning`` triggered by explicit relative imports in - assertion-rewritten package modules. (`#3061 - <https://github.com/pytest-dev/pytest/issues/3061>`_) - -- Fix error in ``pytest.approx`` when dealing with 0-dimension numpy - arrays. (`#3593 <https://github.com/pytest-dev/pytest/issues/3593>`_) - -- No longer raise ``ValueError`` when using the ``get_marker`` API. (`#3605 - <https://github.com/pytest-dev/pytest/issues/3605>`_) - -- Fix problem where log messages with non-ascii characters would not - appear in the output log file. - (`#3630 <https://github.com/pytest-dev/pytest/issues/3630>`_) - -- No longer raise ``AttributeError`` when legacy marks can't be stored in - functions. (`#3631 <https://github.com/pytest-dev/pytest/issues/3631>`_) - - -Improved Documentation ----------------------- - -- The description above the example for ``@pytest.mark.skipif`` now better - matches the code. (`#3611 - <https://github.com/pytest-dev/pytest/issues/3611>`_) - - -Trivial/Internal Changes ------------------------- - -- Internal refactoring: removed unused ``CallSpec2tox ._globalid_args`` - attribute and ``metafunc`` parameter from ``CallSpec2.copy()``. (`#3598 - <https://github.com/pytest-dev/pytest/issues/3598>`_) - -- Silence usage of ``reduce`` warning in Python 2 (`#3609 - <https://github.com/pytest-dev/pytest/issues/3609>`_) - -- Fix usage of ``attr.ib`` deprecated ``convert`` parameter. (`#3653 - <https://github.com/pytest-dev/pytest/issues/3653>`_) - - -pytest 3.6.2 (2018-06-20) -========================= - -Bug Fixes ---------- - -- Fix regression in ``Node.add_marker`` by extracting the mark object of a - ``MarkDecorator``. (`#3555 - <https://github.com/pytest-dev/pytest/issues/3555>`_) - -- Warnings without ``location`` were reported as ``None``. This is corrected to - now report ``<undetermined location>``. (`#3563 - <https://github.com/pytest-dev/pytest/issues/3563>`_) - -- Continue to call finalizers in the stack when a finalizer in a former scope - raises an exception. (`#3569 - <https://github.com/pytest-dev/pytest/issues/3569>`_) - -- Fix encoding error with ``print`` statements in doctests (`#3583 - <https://github.com/pytest-dev/pytest/issues/3583>`_) - - -Improved Documentation ----------------------- - -- Add documentation for the ``--strict`` flag. (`#3549 - <https://github.com/pytest-dev/pytest/issues/3549>`_) - - -Trivial/Internal Changes ------------------------- - -- Update old quotation style to parens in fixture.rst documentation. (`#3525 - <https://github.com/pytest-dev/pytest/issues/3525>`_) - -- Improve display of hint about ``--fulltrace`` with ``KeyboardInterrupt``. - (`#3545 <https://github.com/pytest-dev/pytest/issues/3545>`_) - -- pytest's testsuite is no longer runnable through ``python setup.py test`` -- - instead invoke ``pytest`` or ``tox`` directly. (`#3552 - <https://github.com/pytest-dev/pytest/issues/3552>`_) - -- Fix typo in documentation (`#3567 - <https://github.com/pytest-dev/pytest/issues/3567>`_) - - -pytest 3.6.1 (2018-06-05) -========================= - -Bug Fixes ---------- - -- Fixed a bug where stdout and stderr were logged twice by junitxml when a test - was marked xfail. (`#3491 - <https://github.com/pytest-dev/pytest/issues/3491>`_) - -- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating - ``FixtureInfo``. (`#3498 - <https://github.com/pytest-dev/pytest/issues/3498>`_) - -- Fix assertion rewriter compatibility with libraries that monkey patch - ``file`` objects. (`#3503 - <https://github.com/pytest-dev/pytest/issues/3503>`_) - - -Improved Documentation ----------------------- - -- Added a section on how to use fixtures as factories to the fixture - documentation. (`#3461 <https://github.com/pytest-dev/pytest/issues/3461>`_) - - -Trivial/Internal Changes ------------------------- - -- Enable caching for pip/pre-commit in order to reduce build time on - travis/appveyor. (`#3502 - <https://github.com/pytest-dev/pytest/issues/3502>`_) - -- Switch pytest to the src/ layout as we already suggested it for good practice - - now we implement it as well. (`#3513 - <https://github.com/pytest-dev/pytest/issues/3513>`_) - -- Fix if in tests to support 3.7.0b5, where a docstring handling in AST got - reverted. (`#3530 <https://github.com/pytest-dev/pytest/issues/3530>`_) - -- Remove some python2.5 compatibility code. (`#3529 - <https://github.com/pytest-dev/pytest/issues/3529>`_) - - -pytest 3.6.0 (2018-05-23) -========================= - -Features --------- - -- Revamp the internals of the ``pytest.mark`` implementation with correct per - node handling which fixes a number of long standing bugs caused by the old - design. This introduces new ``Node.iter_markers(name)`` and - ``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to - read the `reasons for the revamp in the docs - <https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration>`_, - or jump over to details about `updating existing code to use the new APIs - <https://docs.pytest.org/en/latest/mark.html#updating-code>`_. (`#3317 - <https://github.com/pytest-dev/pytest/issues/3317>`_) - -- Now when ``@pytest.fixture`` is applied more than once to the same function a - ``ValueError`` is raised. This buggy behavior would cause surprising problems - and if was working for a test suite it was mostly by accident. (`#2334 - <https://github.com/pytest-dev/pytest/issues/2334>`_) - -- Support for Python 3.7's builtin ``breakpoint()`` method, see `Using the - builtin breakpoint function - <https://docs.pytest.org/en/latest/usage.html#breakpoint-builtin>`_ for - details. (`#3180 <https://github.com/pytest-dev/pytest/issues/3180>`_) - -- ``monkeypatch`` now supports a ``context()`` function which acts as a context - manager which undoes all patching done within the ``with`` block. (`#3290 - <https://github.com/pytest-dev/pytest/issues/3290>`_) - -- The ``--pdb`` option now causes KeyboardInterrupt to enter the debugger, - instead of stopping the test session. On python 2.7, hitting CTRL+C again - exits the debugger. On python 3.2 and higher, use CTRL+D. (`#3299 - <https://github.com/pytest-dev/pytest/issues/3299>`_) - -- pytest no longer changes the log level of the root logger when the - ``log-level`` parameter has greater numeric value than that of the level of - the root logger, which makes it play better with custom logging configuration - in user code. (`#3307 <https://github.com/pytest-dev/pytest/issues/3307>`_) - - -Bug Fixes ---------- - -- A rare race-condition which might result in corrupted ``.pyc`` files on - Windows has been hopefully solved. (`#3008 - <https://github.com/pytest-dev/pytest/issues/3008>`_) - -- Also use iter_marker for discovering the marks applying for marker - expressions from the cli to avoid the bad data from the legacy mark storage. - (`#3441 <https://github.com/pytest-dev/pytest/issues/3441>`_) - -- When showing diffs of failed assertions where the contents contain only - whitespace, escape them using ``repr()`` first to make it easy to spot the - differences. (`#3443 <https://github.com/pytest-dev/pytest/issues/3443>`_) - - -Improved Documentation ----------------------- - -- Change documentation copyright year to a range which auto-updates itself each - time it is published. (`#3303 - <https://github.com/pytest-dev/pytest/issues/3303>`_) - - -Trivial/Internal Changes ------------------------- - -- ``pytest`` now depends on the `python-atomicwrites - <https://github.com/untitaker/python-atomicwrites>`_ library. (`#3008 - <https://github.com/pytest-dev/pytest/issues/3008>`_) - -- Update all pypi.python.org URLs to pypi.org. (`#3431 - <https://github.com/pytest-dev/pytest/issues/3431>`_) - -- Detect `pytest_` prefixed hooks using the internal plugin manager since - ``pluggy`` is deprecating the ``implprefix`` argument to ``PluginManager``. - (`#3487 <https://github.com/pytest-dev/pytest/issues/3487>`_) - -- Import ``Mapping`` and ``Sequence`` from ``_pytest.compat`` instead of - directly from ``collections`` in ``python_api.py::approx``. Add ``Mapping`` - to ``_pytest.compat``, import it from ``collections`` on python 2, but from - ``collections.abc`` on Python 3 to avoid a ``DeprecationWarning`` on Python - 3.7 or newer. (`#3497 <https://github.com/pytest-dev/pytest/issues/3497>`_) - - -pytest 3.5.1 (2018-04-23) -========================= - - -Bug Fixes ---------- - -- Reset ``sys.last_type``, ``sys.last_value`` and ``sys.last_traceback`` before - each test executes. Those attributes are added by pytest during the test run - to aid debugging, but were never reset so they would create a leaking - reference to the last failing test's frame which in turn could never be - reclaimed by the garbage collector. (`#2798 - <https://github.com/pytest-dev/pytest/issues/2798>`_) - -- ``pytest.raises`` now raises ``TypeError`` when receiving an unknown keyword - argument. (`#3348 <https://github.com/pytest-dev/pytest/issues/3348>`_) - -- ``pytest.raises`` now works with exception classes that look like iterables. - (`#3372 <https://github.com/pytest-dev/pytest/issues/3372>`_) - - -Improved Documentation ----------------------- - -- Fix typo in ``caplog`` fixture documentation, which incorrectly identified - certain attributes as methods. (`#3406 - <https://github.com/pytest-dev/pytest/issues/3406>`_) - - -Trivial/Internal Changes ------------------------- - -- Added a more indicative error message when parametrizing a function whose - argument takes a default value. (`#3221 - <https://github.com/pytest-dev/pytest/issues/3221>`_) - -- Remove internal ``_pytest.terminal.flatten`` function in favor of - ``more_itertools.collapse``. (`#3330 - <https://github.com/pytest-dev/pytest/issues/3330>`_) - -- Import some modules from ``collections.abc`` instead of ``collections`` as - the former modules trigger ``DeprecationWarning`` in Python 3.7. (`#3339 - <https://github.com/pytest-dev/pytest/issues/3339>`_) - -- record_property is no longer experimental, removing the warnings was - forgotten. (`#3360 <https://github.com/pytest-dev/pytest/issues/3360>`_) - -- Mention in documentation and CLI help that fixtures with leading ``_`` are - printed by ``pytest --fixtures`` only if the ``-v`` option is added. (`#3398 - <https://github.com/pytest-dev/pytest/issues/3398>`_) - - -pytest 3.5.0 (2018-03-21) -========================= - -Deprecations and Removals -------------------------- - -- ``record_xml_property`` fixture is now deprecated in favor of the more - generic ``record_property``. (`#2770 - <https://github.com/pytest-dev/pytest/issues/2770>`_) - -- Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py - files, because they "leak" to the entire directory tree. `See the docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`_ for the rationale behind this decision (`#3084 - <https://github.com/pytest-dev/pytest/issues/3084>`_) - - -Features --------- - -- New ``--show-capture`` command-line option that allows to specify how to - display captured output when tests fail: ``no``, ``stdout``, ``stderr``, - ``log`` or ``all`` (the default). (`#1478 - <https://github.com/pytest-dev/pytest/issues/1478>`_) - -- New ``--rootdir`` command-line option to override the rules for discovering - the root directory. See `customize - <https://docs.pytest.org/en/latest/customize.html>`_ in the documentation for - details. (`#1642 <https://github.com/pytest-dev/pytest/issues/1642>`_) - -- Fixtures are now instantiated based on their scopes, with higher-scoped - fixtures (such as ``session``) being instantiated first than lower-scoped - fixtures (such as ``function``). The relative order of fixtures of the same - scope is kept unchanged, based in their declaration order and their - dependencies. (`#2405 <https://github.com/pytest-dev/pytest/issues/2405>`_) - -- ``record_xml_property`` renamed to ``record_property`` and is now compatible - with xdist, markers and any reporter. ``record_xml_property`` name is now - deprecated. (`#2770 <https://github.com/pytest-dev/pytest/issues/2770>`_) - -- New ``--nf``, ``--new-first`` options: run new tests first followed by the - rest of the tests, in both cases tests are also sorted by the file modified - time, with more recent files coming first. (`#3034 - <https://github.com/pytest-dev/pytest/issues/3034>`_) - -- New ``--last-failed-no-failures`` command-line option that allows to specify - the behavior of the cache plugin's ```--last-failed`` feature when no tests - failed in the last run (or no cache was found): ``none`` or ``all`` (the - default). (`#3139 <https://github.com/pytest-dev/pytest/issues/3139>`_) - -- New ``--doctest-continue-on-failure`` command-line option to enable doctests - to show multiple failures for each snippet, instead of stopping at the first - failure. (`#3149 <https://github.com/pytest-dev/pytest/issues/3149>`_) - -- Captured log messages are added to the ``<system-out>`` tag in the generated - junit xml file if the ``junit_logging`` ini option is set to ``system-out``. - If the value of this ini option is ``system-err``, the logs are written to - ``<system-err>``. The default value for ``junit_logging`` is ``no``, meaning - captured logs are not written to the output file. (`#3156 - <https://github.com/pytest-dev/pytest/issues/3156>`_) - -- Allow the logging plugin to handle ``pytest_runtest_logstart`` and - ``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#3189 - <https://github.com/pytest-dev/pytest/issues/3189>`_) - -- Passing ``--log-cli-level`` in the command-line now automatically activates - live logging. (`#3190 <https://github.com/pytest-dev/pytest/issues/3190>`_) - -- Add command line option ``--deselect`` to allow deselection of individual - tests at collection time. (`#3198 - <https://github.com/pytest-dev/pytest/issues/3198>`_) - -- Captured logs are printed before entering pdb. (`#3204 - <https://github.com/pytest-dev/pytest/issues/3204>`_) - -- Deselected item count is now shown before tests are run, e.g. ``collected X - items / Y deselected``. (`#3213 - <https://github.com/pytest-dev/pytest/issues/3213>`_) - -- The builtin module ``platform`` is now available for use in expressions in - ``pytest.mark``. (`#3236 - <https://github.com/pytest-dev/pytest/issues/3236>`_) - -- The *short test summary info* section now is displayed after tracebacks and - warnings in the terminal. (`#3255 - <https://github.com/pytest-dev/pytest/issues/3255>`_) - -- New ``--verbosity`` flag to set verbosity level explicitly. (`#3296 - <https://github.com/pytest-dev/pytest/issues/3296>`_) - -- ``pytest.approx`` now accepts comparing a numpy array with a scalar. (`#3312 - <https://github.com/pytest-dev/pytest/issues/3312>`_) - - -Bug Fixes ---------- - -- Suppress ``IOError`` when closing the temporary file used for capturing - streams in Python 2.7. (`#2370 - <https://github.com/pytest-dev/pytest/issues/2370>`_) - -- Fixed ``clear()`` method on ``caplog`` fixture which cleared ``records``, but - not the ``text`` property. (`#3297 - <https://github.com/pytest-dev/pytest/issues/3297>`_) - -- During test collection, when stdin is not allowed to be read, the - ``DontReadFromStdin`` object still allow itself to be iterable and resolved - to an iterator without crashing. (`#3314 - <https://github.com/pytest-dev/pytest/issues/3314>`_) - - -Improved Documentation ----------------------- - -- Added a `reference <https://docs.pytest.org/en/latest/reference.html>`_ page - to the docs. (`#1713 <https://github.com/pytest-dev/pytest/issues/1713>`_) - - -Trivial/Internal Changes ------------------------- - -- Change minimum requirement of ``attrs`` to ``17.4.0``. (`#3228 - <https://github.com/pytest-dev/pytest/issues/3228>`_) - -- Renamed example directories so all tests pass when ran from the base - directory. (`#3245 <https://github.com/pytest-dev/pytest/issues/3245>`_) - -- Internal ``mark.py`` module has been turned into a package. (`#3250 - <https://github.com/pytest-dev/pytest/issues/3250>`_) - -- ``pytest`` now depends on the `more-itertools - <https://github.com/erikrose/more-itertools>`_ package. (`#3265 - <https://github.com/pytest-dev/pytest/issues/3265>`_) - -- Added warning when ``[pytest]`` section is used in a ``.cfg`` file passed - with ``-c`` (`#3268 <https://github.com/pytest-dev/pytest/issues/3268>`_) - -- ``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node`` - constructors. (`#3291 <https://github.com/pytest-dev/pytest/issues/3291>`_) - -- Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and - remove old support code for legacy Python versions. (`#3292 - <https://github.com/pytest-dev/pytest/issues/3292>`_) - -- Refactoring to unify how verbosity is handled internally. (`#3296 - <https://github.com/pytest-dev/pytest/issues/3296>`_) - -- Internal refactoring to better integrate with argparse. (`#3304 - <https://github.com/pytest-dev/pytest/issues/3304>`_) - -- Fix a python example when calling a fixture in doc/en/usage.rst (`#3308 - <https://github.com/pytest-dev/pytest/issues/3308>`_) - - -pytest 3.4.2 (2018-03-04) -========================= - -Bug Fixes ---------- - -- Removed progress information when capture option is ``no``. (`#3203 - <https://github.com/pytest-dev/pytest/issues/3203>`_) - -- Refactor check of bindir from ``exists`` to ``isdir``. (`#3241 - <https://github.com/pytest-dev/pytest/issues/3241>`_) - -- Fix ``TypeError`` issue when using ``approx`` with a ``Decimal`` value. - (`#3247 <https://github.com/pytest-dev/pytest/issues/3247>`_) - -- Fix reference cycle generated when using the ``request`` fixture. (`#3249 - <https://github.com/pytest-dev/pytest/issues/3249>`_) - -- ``[tool:pytest]`` sections in ``*.cfg`` files passed by the ``-c`` option are - now properly recognized. (`#3260 - <https://github.com/pytest-dev/pytest/issues/3260>`_) - - -Improved Documentation ----------------------- - -- Add logging plugin to plugins list. (`#3209 - <https://github.com/pytest-dev/pytest/issues/3209>`_) - - -Trivial/Internal Changes ------------------------- - -- Fix minor typo in fixture.rst (`#3259 - <https://github.com/pytest-dev/pytest/issues/3259>`_) - - -pytest 3.4.1 (2018-02-20) -========================= - -Bug Fixes ---------- - -- Move import of ``doctest.UnexpectedException`` to top-level to avoid possible - errors when using ``--pdb``. (`#1810 - <https://github.com/pytest-dev/pytest/issues/1810>`_) - -- Added printing of captured stdout/stderr before entering pdb, and improved a - test which was giving false negatives about output capturing. (`#3052 - <https://github.com/pytest-dev/pytest/issues/3052>`_) - -- Fix ordering of tests using parametrized fixtures which can lead to fixtures - being created more than necessary. (`#3161 - <https://github.com/pytest-dev/pytest/issues/3161>`_) - -- Fix bug where logging happening at hooks outside of "test run" hooks would - cause an internal error. (`#3184 - <https://github.com/pytest-dev/pytest/issues/3184>`_) - -- Detect arguments injected by ``unittest.mock.patch`` decorator correctly when - pypi ``mock.patch`` is installed and imported. (`#3206 - <https://github.com/pytest-dev/pytest/issues/3206>`_) - -- Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner - on what happened: When no exception was raised, the "matching '...'" part got - removed as it falsely implies that an exception was raised but it didn't - match. When a wrong exception was raised, it's now thrown (like - ``pytest.raised()`` without ``match=`` would) instead of complaining about - the unmatched text. (`#3222 - <https://github.com/pytest-dev/pytest/issues/3222>`_) - -- Fixed output capture handling in doctests on macOS. (`#985 - <https://github.com/pytest-dev/pytest/issues/985>`_) - - -Improved Documentation ----------------------- - -- Add Sphinx parameter docs for ``match`` and ``message`` args to - ``pytest.raises``. (`#3202 - <https://github.com/pytest-dev/pytest/issues/3202>`_) - - -Trivial/Internal Changes ------------------------- - -- pytest has changed the publication procedure and is now being published to - PyPI directly from Travis. (`#3060 - <https://github.com/pytest-dev/pytest/issues/3060>`_) - -- Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in - order to comply with the naming convention. (`#3166 - <https://github.com/pytest-dev/pytest/issues/3166>`_) - -- Skip failing pdb/doctest test on mac. (`#985 - <https://github.com/pytest-dev/pytest/issues/985>`_) - - -pytest 3.4.0 (2018-01-30) -========================= - -Deprecations and Removals -------------------------- - -- All pytest classes now subclass ``object`` for better Python 2/3 compatibility. - This should not affect user code except in very rare edge cases. (`#2147 - <https://github.com/pytest-dev/pytest/issues/2147>`_) - - -Features --------- - -- Introduce ``empty_parameter_set_mark`` ini option to select which mark to - apply when ``@pytest.mark.parametrize`` is given an empty set of parameters. - Valid options are ``skip`` (default) and ``xfail``. Note that it is planned - to change the default to ``xfail`` in future releases as this is considered - less error prone. (`#2527 - <https://github.com/pytest-dev/pytest/issues/2527>`_) - -- **Incompatible change**: after community feedback the `logging - <https://docs.pytest.org/en/latest/logging.html>`_ functionality has - undergone some changes. Please consult the `logging documentation - <https://docs.pytest.org/en/latest/logging.html#incompatible-changes-in-pytest-3-4>`_ - for details. (`#3013 <https://github.com/pytest-dev/pytest/issues/3013>`_) - -- Console output falls back to "classic" mode when capturing is disabled (``-s``), - otherwise the output gets garbled to the point of being useless. (`#3038 - <https://github.com/pytest-dev/pytest/issues/3038>`_) - -- New `pytest_runtest_logfinish - <https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_logfinish>`_ - hook which is called when a test item has finished executing, analogous to - `pytest_runtest_logstart - <https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_start>`_. - (`#3101 <https://github.com/pytest-dev/pytest/issues/3101>`_) - -- Improve performance when collecting tests using many fixtures. (`#3107 - <https://github.com/pytest-dev/pytest/issues/3107>`_) - -- New ``caplog.get_records(when)`` method which provides access to the captured - records for the ``"setup"``, ``"call"`` and ``"teardown"`` - testing stages. (`#3117 <https://github.com/pytest-dev/pytest/issues/3117>`_) - -- New fixture ``record_xml_attribute`` that allows modifying and inserting - attributes on the ``<testcase>`` xml node in JUnit reports. (`#3130 - <https://github.com/pytest-dev/pytest/issues/3130>`_) - -- The default cache directory has been renamed from ``.cache`` to - ``.pytest_cache`` after community feedback that the name ``.cache`` did not - make it clear that it was used by pytest. (`#3138 - <https://github.com/pytest-dev/pytest/issues/3138>`_) - -- Colorize the levelname column in the live-log output. (`#3142 - <https://github.com/pytest-dev/pytest/issues/3142>`_) - - -Bug Fixes ---------- - -- Fix hanging pexpect test on MacOS by using flush() instead of wait(). - (`#2022 <https://github.com/pytest-dev/pytest/issues/2022>`_) - -- Fix restoring Python state after in-process pytest runs with the - ``pytester`` plugin; this may break tests using multiple inprocess - pytest runs if later ones depend on earlier ones leaking global interpreter - changes. (`#3016 <https://github.com/pytest-dev/pytest/issues/3016>`_) - -- Fix skipping plugin reporting hook when test aborted before plugin setup - hook. (`#3074 <https://github.com/pytest-dev/pytest/issues/3074>`_) - -- Fix progress percentage reported when tests fail during teardown. (`#3088 - <https://github.com/pytest-dev/pytest/issues/3088>`_) - -- **Incompatible change**: ``-o/--override`` option no longer eats all the - remaining options, which can lead to surprising behavior: for example, - ``pytest -o foo=1 /path/to/test.py`` would fail because ``/path/to/test.py`` - would be considered as part of the ``-o`` command-line argument. One - consequence of this is that now multiple configuration overrides need - multiple ``-o`` flags: ``pytest -o foo=1 -o bar=2``. (`#3103 - <https://github.com/pytest-dev/pytest/issues/3103>`_) - - -Improved Documentation ----------------------- - -- Document hooks (defined with ``historic=True``) which cannot be used with - ``hookwrapper=True``. (`#2423 - <https://github.com/pytest-dev/pytest/issues/2423>`_) - -- Clarify that warning capturing doesn't change the warning filter by default. - (`#2457 <https://github.com/pytest-dev/pytest/issues/2457>`_) - -- Clarify a possible confusion when using pytest_fixture_setup with fixture - functions that return None. (`#2698 - <https://github.com/pytest-dev/pytest/issues/2698>`_) - -- Fix the wording of a sentence on doctest flags used in pytest. (`#3076 - <https://github.com/pytest-dev/pytest/issues/3076>`_) - -- Prefer ``https://*.readthedocs.io`` over ``http://*.rtfd.org`` for links in - the documentation. (`#3092 - <https://github.com/pytest-dev/pytest/issues/3092>`_) - -- Improve readability (wording, grammar) of Getting Started guide (`#3131 - <https://github.com/pytest-dev/pytest/issues/3131>`_) - -- Added note that calling pytest.main multiple times from the same process is - not recommended because of import caching. (`#3143 - <https://github.com/pytest-dev/pytest/issues/3143>`_) - - -Trivial/Internal Changes ------------------------- - -- Show a simple and easy error when keyword expressions trigger a syntax error - (for example, ``"-k foo and import"`` will show an error that you can not use - the ``import`` keyword in expressions). (`#2953 - <https://github.com/pytest-dev/pytest/issues/2953>`_) - -- Change parametrized automatic test id generation to use the ``__name__`` - attribute of functions instead of the fallback argument name plus counter. - (`#2976 <https://github.com/pytest-dev/pytest/issues/2976>`_) - -- Replace py.std with stdlib imports. (`#3067 - <https://github.com/pytest-dev/pytest/issues/3067>`_) - -- Corrected 'you' to 'your' in logging docs. (`#3129 - <https://github.com/pytest-dev/pytest/issues/3129>`_) - - -pytest 3.3.2 (2017-12-25) -========================= - -Bug Fixes ---------- - -- pytester: ignore files used to obtain current user metadata in the fd leak - detector. (`#2784 <https://github.com/pytest-dev/pytest/issues/2784>`_) - -- Fix **memory leak** where objects returned by fixtures were never destructed - by the garbage collector. (`#2981 - <https://github.com/pytest-dev/pytest/issues/2981>`_) - -- Fix conversion of pyargs to filename to not convert symlinks on Python 2. (`#2985 - <https://github.com/pytest-dev/pytest/issues/2985>`_) - -- ``PYTEST_DONT_REWRITE`` is now checked for plugins too rather than only for - test modules. (`#2995 <https://github.com/pytest-dev/pytest/issues/2995>`_) - - -Improved Documentation ----------------------- - -- Add clarifying note about behavior of multiple parametrized arguments (`#3001 - <https://github.com/pytest-dev/pytest/issues/3001>`_) - - -Trivial/Internal Changes ------------------------- - -- Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_, - `#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_) - -- Clean up code by replacing imports and references of ``_ast`` to ``ast``. - (`#3018 <https://github.com/pytest-dev/pytest/issues/3018>`_) - - -pytest 3.3.1 (2017-12-05) -========================= - -Bug Fixes ---------- - -- Fix issue about ``-p no:<plugin>`` having no effect. (`#2920 - <https://github.com/pytest-dev/pytest/issues/2920>`_) - -- Fix regression with warnings that contained non-strings in their arguments in - Python 2. (`#2956 <https://github.com/pytest-dev/pytest/issues/2956>`_) - -- Always escape null bytes when setting ``PYTEST_CURRENT_TEST``. (`#2957 - <https://github.com/pytest-dev/pytest/issues/2957>`_) - -- Fix ``ZeroDivisionError`` when using the ``testmon`` plugin when no tests - were actually collected. (`#2971 - <https://github.com/pytest-dev/pytest/issues/2971>`_) - -- Bring back ``TerminalReporter.writer`` as an alias to - ``TerminalReporter._tw``. This alias was removed by accident in the ``3.3.0`` - release. (`#2984 <https://github.com/pytest-dev/pytest/issues/2984>`_) - -- The ``pytest-capturelog`` plugin is now also blacklisted, avoiding errors when - running pytest with it still installed. (`#3004 - <https://github.com/pytest-dev/pytest/issues/3004>`_) - - -Improved Documentation ----------------------- - -- Fix broken link to plugin ``pytest-localserver``. (`#2963 - <https://github.com/pytest-dev/pytest/issues/2963>`_) - - -Trivial/Internal Changes ------------------------- - -- Update github "bugs" link in ``CONTRIBUTING.rst`` (`#2949 - <https://github.com/pytest-dev/pytest/issues/2949>`_) - - -pytest 3.3.0 (2017-11-23) -========================= - -Deprecations and Removals -------------------------- - -- pytest no longer supports Python **2.6** and **3.3**. Those Python versions - are EOL for some time now and incur maintenance and compatibility costs on - the pytest core team, and following up with the rest of the community we - decided that they will no longer be supported starting on this version. Users - which still require those versions should pin pytest to ``<3.3``. (`#2812 - <https://github.com/pytest-dev/pytest/issues/2812>`_) - -- Remove internal ``_preloadplugins()`` function. This removal is part of the - ``pytest_namespace()`` hook deprecation. (`#2636 - <https://github.com/pytest-dev/pytest/issues/2636>`_) - -- Internally change ``CallSpec2`` to have a list of marks instead of a broken - mapping of keywords. This removes the keywords attribute of the internal - ``CallSpec2`` class. (`#2672 - <https://github.com/pytest-dev/pytest/issues/2672>`_) - -- Remove ParameterSet.deprecated_arg_dict - its not a public api and the lack - of the underscore was a naming error. (`#2675 - <https://github.com/pytest-dev/pytest/issues/2675>`_) - -- Remove the internal multi-typed attribute ``Node._evalskip`` and replace it - with the boolean ``Node._skipped_by_mark``. (`#2767 - <https://github.com/pytest-dev/pytest/issues/2767>`_) - -- The ``params`` list passed to ``pytest.fixture`` is now for - all effects considered immutable and frozen at the moment of the ``pytest.fixture`` - call. Previously the list could be changed before the first invocation of the fixture - allowing for a form of dynamic parametrization (for example, updated from command-line options), - but this was an unwanted implementation detail which complicated the internals and prevented - some internal cleanup. See issue `#2959 <https://github.com/pytest-dev/pytest/issues/2959>`_ - for details and a recommended workaround. - -Features --------- - -- ``pytest_fixture_post_finalizer`` hook can now receive a ``request`` - argument. (`#2124 <https://github.com/pytest-dev/pytest/issues/2124>`_) - -- Replace the old introspection code in compat.py that determines the available - arguments of fixtures with inspect.signature on Python 3 and - funcsigs.signature on Python 2. This should respect ``__signature__`` - declarations on functions. (`#2267 - <https://github.com/pytest-dev/pytest/issues/2267>`_) - -- Report tests with global ``pytestmark`` variable only once. (`#2549 - <https://github.com/pytest-dev/pytest/issues/2549>`_) - -- Now pytest displays the total progress percentage while running tests. The - previous output style can be set by configuring the ``console_output_style`` - setting to ``classic``. (`#2657 <https://github.com/pytest-dev/pytest/issues/2657>`_) - -- Match ``warns`` signature to ``raises`` by adding ``match`` keyword. (`#2708 - <https://github.com/pytest-dev/pytest/issues/2708>`_) - -- pytest now captures and displays output from the standard ``logging`` module. - The user can control the logging level to be captured by specifying options - in ``pytest.ini``, the command line and also during individual tests using - markers. Also, a ``caplog`` fixture is available that enables users to test - the captured log during specific tests (similar to ``capsys`` for example). - For more information, please see the `logging docs - <https://docs.pytest.org/en/latest/logging.html>`_. This feature was - introduced by merging the popular `pytest-catchlog - <https://pypi.org/project/pytest-catchlog/>`_ plugin, thanks to `Thomas Hisch - <https://github.com/thisch>`_. Be advised that during the merging the - backward compatibility interface with the defunct ``pytest-capturelog`` has - been dropped. (`#2794 <https://github.com/pytest-dev/pytest/issues/2794>`_) - -- Add ``allow_module_level`` kwarg to ``pytest.skip()``, enabling to skip the - whole module. (`#2808 <https://github.com/pytest-dev/pytest/issues/2808>`_) - -- Allow setting ``file_or_dir``, ``-c``, and ``-o`` in PYTEST_ADDOPTS. (`#2824 - <https://github.com/pytest-dev/pytest/issues/2824>`_) - -- Return stdout/stderr capture results as a ``namedtuple``, so ``out`` and - ``err`` can be accessed by attribute. (`#2879 - <https://github.com/pytest-dev/pytest/issues/2879>`_) - -- Add ``capfdbinary``, a version of ``capfd`` which returns bytes from - ``readouterr()``. (`#2923 - <https://github.com/pytest-dev/pytest/issues/2923>`_) - -- Add ``capsysbinary`` a version of ``capsys`` which returns bytes from - ``readouterr()``. (`#2934 - <https://github.com/pytest-dev/pytest/issues/2934>`_) - -- Implement feature to skip ``setup.py`` files when run with - ``--doctest-modules``. (`#502 - <https://github.com/pytest-dev/pytest/issues/502>`_) - - -Bug Fixes ---------- - -- Resume output capturing after ``capsys/capfd.disabled()`` context manager. - (`#1993 <https://github.com/pytest-dev/pytest/issues/1993>`_) - -- ``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hooks are now - called for all ``conftest.py`` files. (`#2124 - <https://github.com/pytest-dev/pytest/issues/2124>`_) - -- If an exception happens while loading a plugin, pytest no longer hides the - original traceback. In Python 2 it will show the original traceback with a new - message that explains in which plugin. In Python 3 it will show 2 canonized - exceptions, the original exception while loading the plugin in addition to an - exception that pytest throws about loading a plugin. (`#2491 - <https://github.com/pytest-dev/pytest/issues/2491>`_) - -- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709 - <https://github.com/pytest-dev/pytest/issues/2709>`_) - -- Internal ``pytester`` plugin properly encodes ``bytes`` arguments to - ``utf-8``. (`#2738 <https://github.com/pytest-dev/pytest/issues/2738>`_) - -- ``testdir`` now uses use the same method used by ``tmpdir`` to create its - temporary directory. This changes the final structure of the ``testdir`` - directory slightly, but should not affect usage in normal scenarios and - avoids a number of potential problems. (`#2751 - <https://github.com/pytest-dev/pytest/issues/2751>`_) - -- pytest no longer complains about warnings with unicode messages being - non-ascii compatible even for ascii-compatible messages. As a result of this, - warnings with unicode messages are converted first to an ascii representation - for safety. (`#2809 <https://github.com/pytest-dev/pytest/issues/2809>`_) - -- Change return value of pytest command when ``--maxfail`` is reached from - ``2`` (interrupted) to ``1`` (failed). (`#2845 - <https://github.com/pytest-dev/pytest/issues/2845>`_) - -- Fix issue in assertion rewriting which could lead it to rewrite modules which - should not be rewritten. (`#2939 - <https://github.com/pytest-dev/pytest/issues/2939>`_) - -- Handle marks without description in ``pytest.ini``. (`#2942 - <https://github.com/pytest-dev/pytest/issues/2942>`_) - - -Trivial/Internal Changes ------------------------- - -- pytest now depends on `attrs <https://pypi.org/project/attrs/>`__ for internal - structures to ease code maintainability. (`#2641 - <https://github.com/pytest-dev/pytest/issues/2641>`_) - -- Refactored internal Python 2/3 compatibility code to use ``six``. (`#2642 - <https://github.com/pytest-dev/pytest/issues/2642>`_) - -- Stop vendoring ``pluggy`` - we're missing out on its latest changes for not - much benefit (`#2719 <https://github.com/pytest-dev/pytest/issues/2719>`_) - -- Internal refactor: simplify ascii string escaping by using the - backslashreplace error handler in newer Python 3 versions. (`#2734 - <https://github.com/pytest-dev/pytest/issues/2734>`_) - -- Remove unnecessary mark evaluator in unittest plugin (`#2767 - <https://github.com/pytest-dev/pytest/issues/2767>`_) - -- Calls to ``Metafunc.addcall`` now emit a deprecation warning. This function - is scheduled to be removed in ``pytest-4.0``. (`#2876 - <https://github.com/pytest-dev/pytest/issues/2876>`_) - -- Internal move of the parameterset extraction to a more maintainable place. - (`#2877 <https://github.com/pytest-dev/pytest/issues/2877>`_) - -- Internal refactoring to simplify scope node lookup. (`#2910 - <https://github.com/pytest-dev/pytest/issues/2910>`_) - -- Configure ``pytest`` to prevent pip from installing pytest in unsupported - Python versions. (`#2922 - <https://github.com/pytest-dev/pytest/issues/2922>`_) - - -pytest 3.2.5 (2017-11-15) -========================= - -Bug Fixes ---------- - -- Remove ``py<1.5`` restriction from ``pytest`` as this can cause version - conflicts in some installations. (`#2926 - <https://github.com/pytest-dev/pytest/issues/2926>`_) - - -pytest 3.2.4 (2017-11-13) -========================= - -Bug Fixes ---------- - -- Fix the bug where running with ``--pyargs`` will result in items with - empty ``parent.nodeid`` if run from a different root directory. (`#2775 - <https://github.com/pytest-dev/pytest/issues/2775>`_) - -- Fix issue with ``@pytest.parametrize`` if argnames was specified as keyword arguments. - (`#2819 <https://github.com/pytest-dev/pytest/issues/2819>`_) - -- Strip whitespace from marker names when reading them from INI config. (`#2856 - <https://github.com/pytest-dev/pytest/issues/2856>`_) - -- Show full context of doctest source in the pytest output, if the line number of - failed example in the docstring is < 9. (`#2882 - <https://github.com/pytest-dev/pytest/issues/2882>`_) - -- Match fixture paths against actual path segments in order to avoid matching folders which share a prefix. - (`#2836 <https://github.com/pytest-dev/pytest/issues/2836>`_) - -Improved Documentation ----------------------- - -- Introduce a dedicated section about conftest.py. (`#1505 - <https://github.com/pytest-dev/pytest/issues/1505>`_) - -- Explicitly mention ``xpass`` in the documentation of ``xfail``. (`#1997 - <https://github.com/pytest-dev/pytest/issues/1997>`_) - -- Append example for pytest.param in the example/parametrize document. (`#2658 - <https://github.com/pytest-dev/pytest/issues/2658>`_) - -- Clarify language of proposal for fixtures parameters (`#2893 - <https://github.com/pytest-dev/pytest/issues/2893>`_) - -- List python 3.6 in the documented supported versions in the getting started - document. (`#2903 <https://github.com/pytest-dev/pytest/issues/2903>`_) - -- Clarify the documentation of available fixture scopes. (`#538 - <https://github.com/pytest-dev/pytest/issues/538>`_) - -- Add documentation about the ``python -m pytest`` invocation adding the - current directory to sys.path. (`#911 - <https://github.com/pytest-dev/pytest/issues/911>`_) - - -pytest 3.2.3 (2017-10-03) -========================= - -Bug Fixes ---------- - -- Fix crash in tab completion when no prefix is given. (`#2748 - <https://github.com/pytest-dev/pytest/issues/2748>`_) - -- The equality checking function (``__eq__``) of ``MarkDecorator`` returns - ``False`` if one object is not an instance of ``MarkDecorator``. (`#2758 - <https://github.com/pytest-dev/pytest/issues/2758>`_) - -- When running ``pytest --fixtures-per-test``: don't crash if an item has no - _fixtureinfo attribute (e.g. doctests) (`#2788 - <https://github.com/pytest-dev/pytest/issues/2788>`_) - - -Improved Documentation ----------------------- - -- In help text of ``-k`` option, add example of using ``not`` to not select - certain tests whose names match the provided expression. (`#1442 - <https://github.com/pytest-dev/pytest/issues/1442>`_) - -- Add note in ``parametrize.rst`` about calling ``metafunc.parametrize`` - multiple times. (`#1548 <https://github.com/pytest-dev/pytest/issues/1548>`_) - - -Trivial/Internal Changes ------------------------- - -- Set ``xfail_strict=True`` in pytest's own test suite to catch expected - failures as soon as they start to pass. (`#2722 - <https://github.com/pytest-dev/pytest/issues/2722>`_) - -- Fix typo in example of passing a callable to markers (in example/markers.rst) - (`#2765 <https://github.com/pytest-dev/pytest/issues/2765>`_) - - -pytest 3.2.2 (2017-09-06) -========================= - -Bug Fixes ---------- - -- Calling the deprecated ``request.getfuncargvalue()`` now shows the source of - the call. (`#2681 <https://github.com/pytest-dev/pytest/issues/2681>`_) - -- Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699 - <https://github.com/pytest-dev/pytest/issues/2699>`_) - -- Fixed edge-case during collection: attributes which raised ``pytest.fail`` - when accessed would abort the entire collection. (`#2707 - <https://github.com/pytest-dev/pytest/issues/2707>`_) - -- Fix ``ReprFuncArgs`` with mixed unicode and UTF-8 args. (`#2731 - <https://github.com/pytest-dev/pytest/issues/2731>`_) - - -Improved Documentation ----------------------- - -- In examples on working with custom markers, add examples demonstrating the - usage of ``pytest.mark.MARKER_NAME.with_args`` in comparison with - ``pytest.mark.MARKER_NAME.__call__`` (`#2604 - <https://github.com/pytest-dev/pytest/issues/2604>`_) - -- In one of the simple examples, use ``pytest_collection_modifyitems()`` to skip - tests based on a command-line option, allowing its sharing while preventing a - user error when acessing ``pytest.config`` before the argument parsing. - (`#2653 <https://github.com/pytest-dev/pytest/issues/2653>`_) - - -Trivial/Internal Changes ------------------------- - -- Fixed minor error in 'Good Practices/Manual Integration' code snippet. - (`#2691 <https://github.com/pytest-dev/pytest/issues/2691>`_) - -- Fixed typo in goodpractices.rst. (`#2721 - <https://github.com/pytest-dev/pytest/issues/2721>`_) - -- Improve user guidance regarding ``--resultlog`` deprecation. (`#2739 - <https://github.com/pytest-dev/pytest/issues/2739>`_) - - -pytest 3.2.1 (2017-08-08) -========================= - -Bug Fixes ---------- - -- Fixed small terminal glitch when collecting a single test item. (`#2579 - <https://github.com/pytest-dev/pytest/issues/2579>`_) - -- Correctly consider ``/`` as the file separator to automatically mark plugin - files for rewrite on Windows. (`#2591 <https://github.com/pytest- - dev/pytest/issues/2591>`_) - -- Properly escape test names when setting ``PYTEST_CURRENT_TEST`` environment - variable. (`#2644 <https://github.com/pytest-dev/pytest/issues/2644>`_) - -- Fix error on Windows and Python 3.6+ when ``sys.stdout`` has been replaced - with a stream-like object which does not implement the full ``io`` module - buffer protocol. In particular this affects ``pytest-xdist`` users on the - aforementioned platform. (`#2666 <https://github.com/pytest- - dev/pytest/issues/2666>`_) - - -Improved Documentation ----------------------- - -- Explicitly document which pytest features work with ``unittest``. (`#2626 - <https://github.com/pytest-dev/pytest/issues/2626>`_) - - -pytest 3.2.0 (2017-07-30) -========================= - -Deprecations and Removals -------------------------- - -- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=`` - operators to avoid surprising/inconsistent behavior. See `the approx docs - <https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more - information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_) - -- All old-style specific behavior in current classes in the pytest's API is - considered deprecated at this point and will be removed in a future release. - This affects Python 2 users only and in rare situations. (`#2147 - <https://github.com/pytest-dev/pytest/issues/2147>`_) - -- A deprecation warning is now raised when using marks for parameters - in ``pytest.mark.parametrize``. Use ``pytest.param`` to apply marks to - parameters instead. (`#2427 <https://github.com/pytest-dev/pytest/issues/2427>`_) - - -Features --------- - -- Add support for numpy arrays (and dicts) to approx. (`#1994 - <https://github.com/pytest-dev/pytest/issues/1994>`_) - -- Now test function objects have a ``pytestmark`` attribute containing a list - of marks applied directly to the test function, as opposed to marks inherited - from parent classes or modules. (`#2516 <https://github.com/pytest- - dev/pytest/issues/2516>`_) - -- Collection ignores local virtualenvs by default; ``--collect-in-virtualenv`` - overrides this behavior. (`#2518 <https://github.com/pytest- - dev/pytest/issues/2518>`_) - -- Allow class methods decorated as ``@staticmethod`` to be candidates for - collection as a test function. (Only for Python 2.7 and above. Python 2.6 - will still ignore static methods.) (`#2528 <https://github.com/pytest- - dev/pytest/issues/2528>`_) - -- Introduce ``mark.with_args`` in order to allow passing functions/classes as - sole argument to marks. (`#2540 <https://github.com/pytest- - dev/pytest/issues/2540>`_) - -- New ``cache_dir`` ini option: sets the directory where the contents of the - cache plugin are stored. Directory may be relative or absolute path: if relative path, then - directory is created relative to ``rootdir``, otherwise it is used as is. - Additionally path may contain environment variables which are expanded during - runtime. (`#2543 <https://github.com/pytest-dev/pytest/issues/2543>`_) - -- Introduce the ``PYTEST_CURRENT_TEST`` environment variable that is set with - the ``nodeid`` and stage (``setup``, ``call`` and ``teardown``) of the test - being currently executed. See the `documentation - <https://docs.pytest.org/en/latest/example/simple.html#pytest-current-test- - environment-variable>`_ for more info. (`#2583 <https://github.com/pytest- - dev/pytest/issues/2583>`_) - -- Introduced ``@pytest.mark.filterwarnings`` mark which allows overwriting the - warnings filter on a per test, class or module level. See the `docs - <https://docs.pytest.org/en/latest/warnings.html#pytest-mark- - filterwarnings>`_ for more information. (`#2598 <https://github.com/pytest- - dev/pytest/issues/2598>`_) - -- ``--last-failed`` now remembers forever when a test has failed and only - forgets it if it passes again. This makes it easy to fix a test suite by - selectively running files and fixing tests incrementally. (`#2621 - <https://github.com/pytest-dev/pytest/issues/2621>`_) - -- New ``pytest_report_collectionfinish`` hook which allows plugins to add - messages to the terminal reporting after collection has been finished - successfully. (`#2622 <https://github.com/pytest-dev/pytest/issues/2622>`_) - -- Added support for `PEP-415's <https://www.python.org/dev/peps/pep-0415/>`_ - ``Exception.__suppress_context__``. Now if a ``raise exception from None`` is - caught by pytest, pytest will no longer chain the context in the test report. - The behavior now matches Python's traceback behavior. (`#2631 - <https://github.com/pytest-dev/pytest/issues/2631>`_) - -- Exceptions raised by ``pytest.fail``, ``pytest.skip`` and ``pytest.xfail`` - now subclass BaseException, making them harder to be caught unintentionally - by normal code. (`#580 <https://github.com/pytest-dev/pytest/issues/580>`_) - - -Bug Fixes ---------- - -- Set ``stdin`` to a closed ``PIPE`` in ``pytester.py.Testdir.popen()`` for - avoid unwanted interactive ``pdb`` (`#2023 <https://github.com/pytest- - dev/pytest/issues/2023>`_) - -- Add missing ``encoding`` attribute to ``sys.std*`` streams when using - ``capsys`` capture mode. (`#2375 <https://github.com/pytest- - dev/pytest/issues/2375>`_) - -- Fix terminal color changing to black on Windows if ``colorama`` is imported - in a ``conftest.py`` file. (`#2510 <https://github.com/pytest- - dev/pytest/issues/2510>`_) - -- Fix line number when reporting summary of skipped tests. (`#2548 - <https://github.com/pytest-dev/pytest/issues/2548>`_) - -- capture: ensure that EncodedFile.name is a string. (`#2555 - <https://github.com/pytest-dev/pytest/issues/2555>`_) - -- The options ``--fixtures`` and ``--fixtures-per-test`` will now keep - indentation within docstrings. (`#2574 <https://github.com/pytest- - dev/pytest/issues/2574>`_) - -- doctests line numbers are now reported correctly, fixing `pytest-sugar#122 - <https://github.com/Frozenball/pytest-sugar/issues/122>`_. (`#2610 - <https://github.com/pytest-dev/pytest/issues/2610>`_) - -- Fix non-determinism in order of fixture collection. Adds new dependency - (ordereddict) for Python 2.6. (`#920 <https://github.com/pytest- - dev/pytest/issues/920>`_) - - -Improved Documentation ----------------------- - -- Clarify ``pytest_configure`` hook call order. (`#2539 - <https://github.com/pytest-dev/pytest/issues/2539>`_) - -- Extend documentation for testing plugin code with the ``pytester`` plugin. - (`#971 <https://github.com/pytest-dev/pytest/issues/971>`_) - - -Trivial/Internal Changes ------------------------- - -- Update help message for ``--strict`` to make it clear it only deals with - unregistered markers, not warnings. (`#2444 <https://github.com/pytest- - dev/pytest/issues/2444>`_) - -- Internal code move: move code for pytest.approx/pytest.raises to own files in - order to cut down the size of python.py (`#2489 <https://github.com/pytest- - dev/pytest/issues/2489>`_) - -- Renamed the utility function ``_pytest.compat._escape_strings`` to - ``_ascii_escaped`` to better communicate the function's purpose. (`#2533 - <https://github.com/pytest-dev/pytest/issues/2533>`_) - -- Improve error message for CollectError with skip/skipif. (`#2546 - <https://github.com/pytest-dev/pytest/issues/2546>`_) - -- Emit warning about ``yield`` tests being deprecated only once per generator. - (`#2562 <https://github.com/pytest-dev/pytest/issues/2562>`_) - -- Ensure final collected line doesn't include artifacts of previous write. - (`#2571 <https://github.com/pytest-dev/pytest/issues/2571>`_) - -- Fixed all flake8 errors and warnings. (`#2581 <https://github.com/pytest- - dev/pytest/issues/2581>`_) - -- Added ``fix-lint`` tox environment to run automatic pep8 fixes on the code. - (`#2582 <https://github.com/pytest-dev/pytest/issues/2582>`_) - -- Turn warnings into errors in pytest's own test suite in order to catch - regressions due to deprecations more promptly. (`#2588 - <https://github.com/pytest-dev/pytest/issues/2588>`_) - -- Show multiple issue links in CHANGELOG entries. (`#2620 - <https://github.com/pytest-dev/pytest/issues/2620>`_) - - -pytest 3.1.3 (2017-07-03) -========================= - -Bug Fixes ---------- - -- Fix decode error in Python 2 for doctests in docstrings. (`#2434 - <https://github.com/pytest-dev/pytest/issues/2434>`_) - -- Exceptions raised during teardown by finalizers are now suppressed until all - finalizers are called, with the initial exception reraised. (`#2440 - <https://github.com/pytest-dev/pytest/issues/2440>`_) - -- Fix incorrect "collected items" report when specifying tests on the command- - line. (`#2464 <https://github.com/pytest-dev/pytest/issues/2464>`_) - -- ``deprecated_call`` in context-manager form now captures deprecation warnings - even if the same warning has already been raised. Also, ``deprecated_call`` - will always produce the same error message (previously it would produce - different messages in context-manager vs. function-call mode). (`#2469 - <https://github.com/pytest-dev/pytest/issues/2469>`_) - -- Fix issue where paths collected by pytest could have triple leading ``/`` - characters. (`#2475 <https://github.com/pytest-dev/pytest/issues/2475>`_) - -- Fix internal error when trying to detect the start of a recursive traceback. - (`#2486 <https://github.com/pytest-dev/pytest/issues/2486>`_) - - -Improved Documentation ----------------------- - -- Explicitly state for which hooks the calls stop after the first non-None - result. (`#2493 <https://github.com/pytest-dev/pytest/issues/2493>`_) - - -Trivial/Internal Changes ------------------------- - -- Create invoke tasks for updating the vendored packages. (`#2474 - <https://github.com/pytest-dev/pytest/issues/2474>`_) - -- Update copyright dates in LICENSE, README.rst and in the documentation. - (`#2499 <https://github.com/pytest-dev/pytest/issues/2499>`_) - - -pytest 3.1.2 (2017-06-08) -========================= - -Bug Fixes ---------- - -- Required options added via ``pytest_addoption`` will no longer prevent using - --help without passing them. (#1999) - -- Respect ``python_files`` in assertion rewriting. (#2121) - -- Fix recursion error detection when frames in the traceback contain objects - that can't be compared (like ``numpy`` arrays). (#2459) - -- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only - when the message contains non-ascii unicode (Python 2 only). (#2463) - -- Added a workaround for Python 3.6 ``WindowsConsoleIO`` breaking due to Pytests's - ``FDCapture``. Other code using console handles might still be affected by the - very same issue and might require further workarounds/fixes, i.e. ``colorama``. - (#2467) - - -Improved Documentation ----------------------- - -- Fix internal API links to ``pluggy`` objects. (#2331) - -- Make it clear that ``pytest.xfail`` stops test execution at the calling point - and improve overall flow of the ``skipping`` docs. (#810) - - -pytest 3.1.1 (2017-05-30) -========================= - -Bug Fixes ---------- - -- pytest warning capture no longer overrides existing warning filters. The - previous behaviour would override all filters and caused regressions in test - suites which configure warning filters to match their needs. Note that as a - side-effect of this is that ``DeprecationWarning`` and - ``PendingDeprecationWarning`` are no longer shown by default. (#2430) - -- Fix issue with non-ascii contents in doctest text files. (#2434) - -- Fix encoding errors for unicode warnings in Python 2. (#2436) - -- ``pytest.deprecated_call`` now captures ``PendingDeprecationWarning`` in - context manager form. (#2441) - - -Improved Documentation ----------------------- - -- Addition of towncrier for changelog management. (#2390) - - -3.1.0 (2017-05-22) -================== - - -New Features ------------- - -* The ``pytest-warnings`` plugin has been integrated into the core and now ``pytest`` automatically - captures and displays warnings at the end of the test session. - - .. warning:: - - This feature may disrupt test suites which apply and treat warnings themselves, and can be - disabled in your ``pytest.ini``: - - .. code-block:: ini - - [pytest] - addopts = -p no:warnings - - See the `warnings documentation page <https://docs.pytest.org/en/latest/warnings.html>`_ for more - information. - - Thanks `@nicoddemus`_ for the PR. - -* Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (`#533`_). - -* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. - Thanks `@wheerd`_ for the PR (`#2101`_). - -* ``pytest.warns`` now checks for subclass relationship rather than - class equality. Thanks `@lesteve`_ for the PR (`#2166`_) - -* ``pytest.raises`` now asserts that the error message matches a text or regex - with the ``match`` keyword argument. Thanks `@Kriechi`_ for the PR. - -* ``pytest.param`` can be used to declare test parameter sets with marks and test ids. - Thanks `@RonnyPfannschmidt`_ for the PR. - - -Changes -------- - -* remove all internal uses of pytest_namespace hooks, - this is to prepare the removal of preloadconfig in pytest 4.0 - Thanks to `@RonnyPfannschmidt`_ for the PR. - -* pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR. - -* It is now possible to skip test classes from being collected by setting a - ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks - to `@syre`_ for the report and `@lwm`_ for the PR. - -* Change junitxml.py to produce reports that comply with Junitxml schema. - If the same test fails with failure in call and then errors in teardown - we split testcase element into two, one containing the error and the other - the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR. - -* Testcase reports with a ``url`` attribute will now properly write this to junitxml. - Thanks `@fushi`_ for the PR (`#1874`_). - -* Remove common items from dict comparison output when verbosity=1. Also update - the truncation message to make it clearer that pytest truncates all - assertion messages if verbosity < 2 (`#1512`_). - Thanks `@mattduck`_ for the PR - -* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use - ``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for - the PR (`#1952`_). - -* fix `#2013`_: turn RecordedWarning into ``namedtuple``, - to give it a comprehensible repr while preventing unwarranted modification. - -* fix `#2208`_: ensure an iteration limit for _pytest.compat.get_real_func. - Thanks `@RonnyPfannschmidt`_ for the report and PR. - -* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This - makes it easy to write hooks for plugins which will be loaded during collection, for example using the - ``pytest_plugins`` special variable (`#1821`_). - Thanks `@nicoddemus`_ for the PR. - -* Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an - additional parameter. - Thanks `@unsignedint`_ for the PR. - -* Add ``venv`` to the default ``norecursedirs`` setting. - Thanks `@The-Compiler`_ for the PR. - -* ``PluginManager.import_plugin`` now accepts unicode plugin names in Python 2. - Thanks `@reutsharabani`_ for the PR. - -* fix `#2308`_: When using both ``--lf`` and ``--ff``, only the last failed tests are run. - Thanks `@ojii`_ for the PR. - -* Replace minor/patch level version numbers in the documentation with placeholders. - This significantly reduces change-noise as different contributors regnerate - the documentation on different platforms. - Thanks `@RonnyPfannschmidt`_ for the PR. - -* fix `#2391`_: consider pytest_plugins on all plugin modules - Thanks `@RonnyPfannschmidt`_ for the PR. - - -Bug Fixes ---------- - -* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer`` - while using ``capsys`` fixture in python 3. (`#1407`_). - Thanks to `@asottile`_. - -* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather - than ValueErrors in the ``fileno`` method (`#2276`_). - Thanks `@metasyn`_ and `@vlad-dragos`_ for the PR. - -* Fix exception formatting while importing modules when the exception message - contains non-ascii characters (`#2336`_). - Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR. - -* Added documentation related to issue (`#1937`_) - Thanks `@skylarjhdownes`_ for the PR. - -* Allow collecting files with any file extension as Python modules (`#2369`_). - Thanks `@Kodiologist`_ for the PR. - -* Show the correct error message when collect "parametrize" func with wrong args (`#2383`_). - Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR. - - -.. _@davidszotten: https://github.com/davidszotten -.. _@fabioz: https://github.com/fabioz -.. _@fogo: https://github.com/fogo -.. _@fushi: https://github.com/fushi -.. _@Kodiologist: https://github.com/Kodiologist -.. _@Kriechi: https://github.com/Kriechi -.. _@mandeep: https://github.com/mandeep -.. _@mattduck: https://github.com/mattduck -.. _@metasyn: https://github.com/metasyn -.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE -.. _@ojii: https://github.com/ojii -.. _@reutsharabani: https://github.com/reutsharabani -.. _@robin0371: https://github.com/robin0371 -.. _@skylarjhdownes: https://github.com/skylarjhdownes -.. _@unsignedint: https://github.com/unsignedint -.. _@wheerd: https://github.com/wheerd - - -.. _#1407: https://github.com/pytest-dev/pytest/issues/1407 -.. _#1512: https://github.com/pytest-dev/pytest/issues/1512 -.. _#1821: https://github.com/pytest-dev/pytest/issues/1821 -.. _#1874: https://github.com/pytest-dev/pytest/pull/1874 -.. _#1937: https://github.com/pytest-dev/pytest/issues/1937 -.. _#1952: https://github.com/pytest-dev/pytest/pull/1952 -.. _#2007: https://github.com/pytest-dev/pytest/issues/2007 -.. _#2013: https://github.com/pytest-dev/pytest/issues/2013 -.. _#2101: https://github.com/pytest-dev/pytest/pull/2101 -.. _#2166: https://github.com/pytest-dev/pytest/pull/2166 -.. _#2208: https://github.com/pytest-dev/pytest/issues/2208 -.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 -.. _#2276: https://github.com/pytest-dev/pytest/issues/2276 -.. _#2308: https://github.com/pytest-dev/pytest/issues/2308 -.. _#2336: https://github.com/pytest-dev/pytest/issues/2336 -.. _#2369: https://github.com/pytest-dev/pytest/issues/2369 -.. _#2383: https://github.com/pytest-dev/pytest/issues/2383 -.. _#2391: https://github.com/pytest-dev/pytest/issues/2391 -.. _#533: https://github.com/pytest-dev/pytest/issues/533 - - - -3.0.7 (2017-03-14) -================== - - -* Fix issue in assertion rewriting breaking due to modules silently discarding - other modules when importing fails - Notably, importing the ``anydbm`` module is fixed. (`#2248`_). - Thanks `@pfhayes`_ for the PR. - -* junitxml: Fix problematic case where system-out tag occurred twice per testcase - element in the XML report. Thanks `@kkoukiou`_ for the PR. - -* Fix regression, pytest now skips unittest correctly if run with ``--pdb`` - (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR. - -* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_). - Thanks to `@bluetech`_. - -* ``--override-ini`` now correctly overrides some fundamental options like ``python_files`` (`#2238`_). - Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR. - -* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). - Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. - -* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test. - Thanks `@omerhadari`_ for the PR. - -* Skipping plugin now also works with test items generated by custom collectors (`#2231`_). - Thanks to `@vidartf`_. - -* Fix trailing whitespace in console output if no .ini file presented (`#2281`_). Thanks `@fbjorn`_ for the PR. - -* Conditionless ``xfail`` markers no longer rely on the underlying test item - being an instance of ``PyobjMixin``, and can therefore apply to tests not - collected by the built-in python test collector. Thanks `@barneygale`_ for the - PR. - - -.. _@pfhayes: https://github.com/pfhayes -.. _@bluetech: https://github.com/bluetech -.. _@gst: https://github.com/gst -.. _@sirex: https://github.com/sirex -.. _@vidartf: https://github.com/vidartf -.. _@kkoukiou: https://github.com/KKoukiou -.. _@omerhadari: https://github.com/omerhadari -.. _@fbjorn: https://github.com/fbjorn - -.. _#2248: https://github.com/pytest-dev/pytest/issues/2248 -.. _#2137: https://github.com/pytest-dev/pytest/issues/2137 -.. _#2160: https://github.com/pytest-dev/pytest/issues/2160 -.. _#2231: https://github.com/pytest-dev/pytest/issues/2231 -.. _#2234: https://github.com/pytest-dev/pytest/issues/2234 -.. _#2238: https://github.com/pytest-dev/pytest/issues/2238 -.. _#2281: https://github.com/pytest-dev/pytest/issues/2281 - -.. _PEP-479: https://www.python.org/dev/peps/pep-0479/ - - -3.0.6 (2017-01-22) -================== - -* pytest no longer generates ``PendingDeprecationWarning`` from its own operations, which was introduced by mistake in version ``3.0.5`` (`#2118`_). - Thanks to `@nicoddemus`_ for the report and `@RonnyPfannschmidt`_ for the PR. - - -* pytest no longer recognizes coroutine functions as yield tests (`#2129`_). - Thanks to `@malinoff`_ for the PR. - -* Plugins loaded by the ``PYTEST_PLUGINS`` environment variable are now automatically - considered for assertion rewriting (`#2185`_). - Thanks `@nicoddemus`_ for the PR. - -* Improve error message when pytest.warns fails (`#2150`_). The type(s) of the - expected warnings and the list of caught warnings is added to the - error message. Thanks `@lesteve`_ for the PR. - -* Fix ``pytester`` internal plugin to work correctly with latest versions of - ``zope.interface`` (`#1989`_). Thanks `@nicoddemus`_ for the PR. - -* Assert statements of the ``pytester`` plugin again benefit from assertion rewriting (`#1920`_). - Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR. - -* Specifying tests with colons like ``test_foo.py::test_bar`` for tests in - subdirectories with ini configuration files now uses the correct ini file - (`#2148`_). Thanks `@pelme`_. - -* Fail ``testdir.runpytest().assert_outcomes()`` explicitly if the pytest - terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR. - - -.. _@barneygale: https://github.com/barneygale -.. _@lesteve: https://github.com/lesteve -.. _@malinoff: https://github.com/malinoff -.. _@pelme: https://github.com/pelme -.. _@eli-b: https://github.com/eli-b - -.. _#2118: https://github.com/pytest-dev/pytest/issues/2118 - -.. _#1989: https://github.com/pytest-dev/pytest/issues/1989 -.. _#1920: https://github.com/pytest-dev/pytest/issues/1920 -.. _#2129: https://github.com/pytest-dev/pytest/issues/2129 -.. _#2148: https://github.com/pytest-dev/pytest/issues/2148 -.. _#2150: https://github.com/pytest-dev/pytest/issues/2150 -.. _#2185: https://github.com/pytest-dev/pytest/issues/2185 - - -3.0.5 (2016-12-05) -================== - -* Add warning when not passing ``option=value`` correctly to ``-o/--override-ini`` (`#2105`_). - Also improved the help documentation. Thanks to `@mbukatov`_ for the report and - `@lwm`_ for the PR. - -* Now ``--confcutdir`` and ``--junit-xml`` are properly validated if they are directories - and filenames, respectively (`#2089`_ and `#2078`_). Thanks to `@lwm`_ for the PR. - -* Add hint to error message hinting possible missing ``__init__.py`` (`#478`_). Thanks `@DuncanBetts`_. - -* More accurately describe when fixture finalization occurs in documentation (`#687`_). Thanks `@DuncanBetts`_. - -* Provide ``:ref:`` targets for ``recwarn.rst`` so we can use intersphinx referencing. - Thanks to `@dupuy`_ for the report and `@lwm`_ for the PR. - -* In Python 2, use a simple ``+-`` ASCII string in the string representation of ``pytest.approx`` (for example ``"4 +- 4.0e-06"``) - because it is brittle to handle that in different contexts and representations internally in pytest - which can result in bugs such as `#2111`_. In Python 3, the representation still uses ``±`` (for example ``4 ± 4.0e-06``). - Thanks `@kerrick-lyft`_ for the report and `@nicoddemus`_ for the PR. - -* Using ``item.Function``, ``item.Module``, etc., is now issuing deprecation warnings, prefer - ``pytest.Function``, ``pytest.Module``, etc., instead (`#2034`_). - Thanks `@nmundar`_ for the PR. - -* Fix error message using ``approx`` with complex numbers (`#2082`_). - Thanks `@adler-j`_ for the report and `@nicoddemus`_ for the PR. - -* Fixed false-positives warnings from assertion rewrite hook for modules imported more than - once by the ``pytest_plugins`` mechanism. - Thanks `@nicoddemus`_ for the PR. - -* Remove an internal cache which could cause hooks from ``conftest.py`` files in - sub-directories to be called in other directories incorrectly (`#2016`_). - Thanks `@d-b-w`_ for the report and `@nicoddemus`_ for the PR. - -* Remove internal code meant to support earlier Python 3 versions that produced the side effect - of leaving ``None`` in ``sys.modules`` when expressions were evaluated by pytest (for example passing a condition - as a string to ``pytest.mark.skipif``)(`#2103`_). - Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR. - -* Cope gracefully with a .pyc file with no matching .py file (`#2038`_). Thanks - `@nedbat`_. - -.. _@syre: https://github.com/syre -.. _@adler-j: https://github.com/adler-j -.. _@d-b-w: https://bitbucket.org/d-b-w/ -.. _@DuncanBetts: https://github.com/DuncanBetts -.. _@dupuy: https://bitbucket.org/dupuy/ -.. _@kerrick-lyft: https://github.com/kerrick-lyft -.. _@lwm: https://github.com/lwm -.. _@mbukatov: https://github.com/mbukatov -.. _@nedbat: https://github.com/nedbat -.. _@nmundar: https://github.com/nmundar - -.. _#2016: https://github.com/pytest-dev/pytest/issues/2016 -.. _#2034: https://github.com/pytest-dev/pytest/issues/2034 -.. _#2038: https://github.com/pytest-dev/pytest/issues/2038 -.. _#2078: https://github.com/pytest-dev/pytest/issues/2078 -.. _#2082: https://github.com/pytest-dev/pytest/issues/2082 -.. _#2089: https://github.com/pytest-dev/pytest/issues/2089 -.. _#2103: https://github.com/pytest-dev/pytest/issues/2103 -.. _#2105: https://github.com/pytest-dev/pytest/issues/2105 -.. _#2111: https://github.com/pytest-dev/pytest/issues/2111 -.. _#478: https://github.com/pytest-dev/pytest/issues/478 -.. _#687: https://github.com/pytest-dev/pytest/issues/687 - - -3.0.4 (2016-11-09) -================== - -* Import errors when collecting test modules now display the full traceback (`#1976`_). - Thanks `@cwitty`_ for the report and `@nicoddemus`_ for the PR. - -* Fix confusing command-line help message for custom options with two or more ``metavar`` properties (`#2004`_). - Thanks `@okulynyak`_ and `@davehunt`_ for the report and `@nicoddemus`_ for the PR. - -* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_). - Thanks `@nicoddemus`_ for the PR. - -* Fixed cyclic reference when ``pytest.raises`` is used in context-manager form (`#1965`_). Also as a - result of this fix, ``sys.exc_info()`` is left empty in both context-manager and function call usages. - Previously, ``sys.exc_info`` would contain the exception caught by the context manager, - even when the expected exception occurred. - Thanks `@MSeifert04`_ for the report and the PR. - -* Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but - were later marked explicitly by ``pytest.register_assert_rewrite`` - or implicitly as a plugin (`#2005`_). - Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR. - -* Report teardown output on test failure (`#442`_). - Thanks `@matclab`_ for the PR. - -* Fix teardown error message in generated xUnit XML. - Thanks `@gdyuldin`_ for the PR. - -* Properly handle exceptions in ``multiprocessing`` tasks (`#1984`_). - Thanks `@adborden`_ for the report and `@nicoddemus`_ for the PR. - -* Clean up unittest TestCase objects after tests are complete (`#1649`_). - Thanks `@d_b_w`_ for the report and PR. - - -.. _@adborden: https://github.com/adborden -.. _@cwitty: https://github.com/cwitty -.. _@d_b_w: https://github.com/d_b_w -.. _@gdyuldin: https://github.com/gdyuldin -.. _@matclab: https://github.com/matclab -.. _@MSeifert04: https://github.com/MSeifert04 -.. _@okulynyak: https://github.com/okulynyak - -.. _#442: https://github.com/pytest-dev/pytest/issues/442 -.. _#1965: https://github.com/pytest-dev/pytest/issues/1965 -.. _#1976: https://github.com/pytest-dev/pytest/issues/1976 -.. _#1984: https://github.com/pytest-dev/pytest/issues/1984 -.. _#1998: https://github.com/pytest-dev/pytest/issues/1998 -.. _#2004: https://github.com/pytest-dev/pytest/issues/2004 -.. _#2005: https://github.com/pytest-dev/pytest/issues/2005 -.. _#1649: https://github.com/pytest-dev/pytest/issues/1649 - - -3.0.3 (2016-09-28) -================== - -* The ``ids`` argument to ``parametrize`` again accepts ``unicode`` strings - in Python 2 (`#1905`_). - Thanks `@philpep`_ for the report and `@nicoddemus`_ for the PR. - -* Assertions are now being rewritten for plugins in development mode - (``pip install -e``) (`#1934`_). - Thanks `@nicoddemus`_ for the PR. - -* Fix pkg_resources import error in Jython projects (`#1853`_). - Thanks `@raquel-ucl`_ for the PR. - -* Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception - in Python 3 (`#1944`_). - Thanks `@axil`_ for the PR. - -* Explain a bad scope value passed to ``@fixture`` declarations or - a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR. - -* This version includes ``pluggy-0.4.0``, which correctly handles - ``VersionConflict`` errors in plugins (`#704`_). - Thanks `@nicoddemus`_ for the PR. - - -.. _@philpep: https://github.com/philpep -.. _@raquel-ucl: https://github.com/raquel-ucl -.. _@axil: https://github.com/axil -.. _@tgoodlet: https://github.com/tgoodlet -.. _@vlad-dragos: https://github.com/vlad-dragos - -.. _#1853: https://github.com/pytest-dev/pytest/issues/1853 -.. _#1905: https://github.com/pytest-dev/pytest/issues/1905 -.. _#1934: https://github.com/pytest-dev/pytest/issues/1934 -.. _#1944: https://github.com/pytest-dev/pytest/issues/1944 -.. _#704: https://github.com/pytest-dev/pytest/issues/704 - - - - -3.0.2 (2016-09-01) -================== - -* Improve error message when passing non-string ids to ``pytest.mark.parametrize`` (`#1857`_). - Thanks `@okken`_ for the report and `@nicoddemus`_ for the PR. - -* Add ``buffer`` attribute to stdin stub class ``pytest.capture.DontReadFromInput`` - Thanks `@joguSD`_ for the PR. - -* Fix ``UnicodeEncodeError`` when string comparison with unicode has failed. (`#1864`_) - Thanks `@AiOO`_ for the PR. - -* ``pytest_plugins`` is now handled correctly if defined as a string (as opposed as - a sequence of strings) when modules are considered for assertion rewriting. - Due to this bug, much more modules were being rewritten than necessary - if a test suite uses ``pytest_plugins`` to load internal plugins (`#1888`_). - Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR (`#1891`_). - -* Do not call tearDown and cleanups when running tests from - ``unittest.TestCase`` subclasses with ``--pdb`` - enabled. This allows proper post mortem debugging for all applications - which have significant logic in their tearDown machinery (`#1890`_). Thanks - `@mbyt`_ for the PR. - -* Fix use of deprecated ``getfuncargvalue`` method in the internal doctest plugin. - Thanks `@ViviCoder`_ for the report (`#1898`_). - -.. _@joguSD: https://github.com/joguSD -.. _@AiOO: https://github.com/AiOO -.. _@mbyt: https://github.com/mbyt -.. _@ViviCoder: https://github.com/ViviCoder - -.. _#1857: https://github.com/pytest-dev/pytest/issues/1857 -.. _#1864: https://github.com/pytest-dev/pytest/issues/1864 -.. _#1888: https://github.com/pytest-dev/pytest/issues/1888 -.. _#1891: https://github.com/pytest-dev/pytest/pull/1891 -.. _#1890: https://github.com/pytest-dev/pytest/issues/1890 -.. _#1898: https://github.com/pytest-dev/pytest/issues/1898 - - -3.0.1 (2016-08-23) -================== - -* Fix regression when ``importorskip`` is used at module level (`#1822`_). - Thanks `@jaraco`_ and `@The-Compiler`_ for the report and `@nicoddemus`_ for the PR. - -* Fix parametrization scope when session fixtures are used in conjunction - with normal parameters in the same call (`#1832`_). - Thanks `@The-Compiler`_ for the report, `@Kingdread`_ and `@nicoddemus`_ for the PR. - -* Fix internal error when parametrizing tests or fixtures using an empty ``ids`` argument (`#1849`_). - Thanks `@OPpuolitaival`_ for the report and `@nicoddemus`_ for the PR. - -* Fix loader error when running ``pytest`` embedded in a zipfile. - Thanks `@mbachry`_ for the PR. - - -.. _@Kingdread: https://github.com/Kingdread -.. _@mbachry: https://github.com/mbachry -.. _@OPpuolitaival: https://github.com/OPpuolitaival - -.. _#1822: https://github.com/pytest-dev/pytest/issues/1822 -.. _#1832: https://github.com/pytest-dev/pytest/issues/1832 -.. _#1849: https://github.com/pytest-dev/pytest/issues/1849 - - -3.0.0 (2016-08-18) -================== - -**Incompatible changes** - - -A number of incompatible changes were made in this release, with the intent of removing features deprecated for a long -time or change existing behaviors in order to make them less surprising/more useful. - -* Reinterpretation mode has now been removed. Only plain and rewrite - mode are available, consequently the ``--assert=reinterp`` option is - no longer available. This also means files imported from plugins or - ``conftest.py`` will not benefit from improved assertions by - default, you should use ``pytest.register_assert_rewrite()`` to - explicitly turn on assertion rewriting for those files. Thanks - `@flub`_ for the PR. - -* The following deprecated commandline options were removed: - - * ``--genscript``: no longer supported; - * ``--no-assert``: use ``--assert=plain`` instead; - * ``--nomagic``: use ``--assert=plain`` instead; - * ``--report``: use ``-r`` instead; - - Thanks to `@RedBeardCode`_ for the PR (`#1664`_). - -* ImportErrors in plugins now are a fatal error instead of issuing a - pytest warning (`#1479`_). Thanks to `@The-Compiler`_ for the PR. - -* Removed support code for Python 3 versions < 3.3 (`#1627`_). - -* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points - were never documented and a leftover from a pre-virtualenv era. These entry - points also created broken entry points in wheels, so removing them also - removes a source of confusion for users (`#1632`_). - Thanks `@obestwalter`_ for the PR. - -* ``pytest.skip()`` now raises an error when used to decorate a test function, - as opposed to its original intent (to imperatively skip a test inside a test function). Previously - this usage would cause the entire module to be skipped (`#607`_). - Thanks `@omarkohl`_ for the complete PR (`#1519`_). - -* Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C - anyway as soon as they see collection errors, so pytest might as well make that the default behavior (`#1421`_). - A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour. - Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_). - -* Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module. - -* Raise a helpful failure message when requesting a parametrized fixture at runtime, - e.g. with ``request.getfixturevalue``. Previously these parameters were simply - never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])`` - only ran once (`#460`_). - Thanks to `@nikratio`_ for the bug report, `@RedBeardCode`_ and `@tomviner`_ for the PR. - -* ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch`` - so it doesn't conflict with the ``monkeypatch`` fixture. - -* ``--exitfirst / -x`` can now be overridden by a following ``--maxfail=N`` - and is just a synonym for ``--maxfail=1``. - - -**New Features** - -* Support nose-style ``__test__`` attribute on methods of classes, - including unittest-style Classes. If set to ``False``, the test will not be - collected. - -* New ``doctest_namespace`` fixture for injecting names into the - namespace in which doctests run. - Thanks `@milliams`_ for the complete PR (`#1428`_). - -* New ``--doctest-report`` option available to change the output format of diffs - when running (failing) doctests (implements `#1749`_). - Thanks `@hartym`_ for the PR. - -* New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name - for a fixture (to solve the funcarg-shadowing-fixture problem). - Thanks `@novas0x2a`_ for the complete PR (`#1444`_). - -* New ``approx()`` function for easily comparing floating-point numbers in - tests. - Thanks `@kalekundert`_ for the complete PR (`#1441`_). - -* Ability to add global properties in the final xunit output file by accessing - the internal ``junitxml`` plugin (experimental). - Thanks `@tareqalayan`_ for the complete PR `#1454`_). - -* New ``ExceptionInfo.match()`` method to match a regular expression on the - string representation of an exception (`#372`_). - Thanks `@omarkohl`_ for the complete PR (`#1502`_). - -* ``__tracebackhide__`` can now also be set to a callable which then can decide - whether to filter the traceback based on the ``ExceptionInfo`` object passed - to it. Thanks `@The-Compiler`_ for the complete PR (`#1526`_). - -* New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide - friendly strings for custom types. - Thanks `@palaviv`_ for the PR. - -* ``capsys`` and ``capfd`` now have a ``disabled()`` context-manager method, which - can be used to temporarily disable capture within a test. - Thanks `@nicoddemus`_ for the PR. - -* New cli flag ``--fixtures-per-test``: shows which fixtures are being used - for each selected test item. Features doc strings of fixtures by default. - Can also show where fixtures are defined if combined with ``-v``. - Thanks `@hackebrot`_ for the PR. - -* Introduce ``pytest`` command as recommended entry point. Note that ``py.test`` - still works and is not scheduled for removal. Closes proposal - `#1629`_. Thanks `@obestwalter`_ and `@davehunt`_ for the complete PR - (`#1633`_). - -* New cli flags: - - + ``--setup-plan``: performs normal collection and reports - the potential setup and teardown and does not execute any fixtures and tests; - + ``--setup-only``: performs normal collection, executes setup and teardown of - fixtures and reports them; - + ``--setup-show``: performs normal test execution and additionally shows - setup and teardown of fixtures; - + ``--keep-duplicates``: py.test now ignores duplicated paths given in the command - line. To retain the previous behavior where the same test could be run multiple - times by specifying it in the command-line multiple times, pass the ``--keep-duplicates`` - argument (`#1609`_); - - Thanks `@d6e`_, `@kvas-it`_, `@sallner`_, `@ioggstream`_ and `@omarkohl`_ for the PRs. - -* New CLI flag ``--override-ini``/``-o``: overrides values from the ini file. - For example: ``"-o xfail_strict=True"``'. - Thanks `@blueyed`_ and `@fengxx`_ for the PR. - -* New hooks: - - + ``pytest_fixture_setup(fixturedef, request)``: executes fixture setup; - + ``pytest_fixture_post_finalizer(fixturedef)``: called after the fixture's - finalizer and has access to the fixture's result cache. - - Thanks `@d6e`_, `@sallner`_. - -* Issue warnings for asserts whose test is a tuple literal. Such asserts will - never fail because tuples are always truthy and are usually a mistake - (see `#1562`_). Thanks `@kvas-it`_, for the PR. - -* Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``). - Thanks to `@anntzer`_ for the PR. - - -**Changes** - -* Plugins now benefit from assertion rewriting. Thanks - `@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR. - -* Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict - mode and ``"failed"`` in strict mode. Thanks to `@hackebrot`_ for the PR - (`#1795`_) and `@gprasad84`_ for report (`#1546`_). - -* Tests marked with ``xfail(strict=False)`` (the default) now appear in - JUnitXML reports as passing tests instead of skipped. - Thanks to `@hackebrot`_ for the PR (`#1795`_). - -* Highlight path of the file location in the error report to make it easier to copy/paste. - Thanks `@suzaku`_ for the PR (`#1778`_). - -* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like - those marked with the ``@pytest.yield_fixture`` decorator. This change renders - ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements - the preferred way to write teardown code (`#1461`_). - Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR. - -* Explicitly passed parametrize ids do not get escaped to ascii (`#1351`_). - Thanks `@ceridwen`_ for the PR. - -* Fixtures are now sorted in the error message displayed when an unknown - fixture is declared in a test function. - Thanks `@nicoddemus`_ for the PR. - -* ``pytest_terminal_summary`` hook now receives the ``exitstatus`` - of the test session as argument. Thanks `@blueyed`_ for the PR (`#1809`_). - -* Parametrize ids can accept ``None`` as specific test id, in which case the - automatically generated id for that argument will be used. - Thanks `@palaviv`_ for the complete PR (`#1468`_). - -* The parameter to xunit-style setup/teardown methods (``setup_method``, - ``setup_module``, etc.) is now optional and may be omitted. - Thanks `@okken`_ for bringing this to attention and `@nicoddemus`_ for the PR. - -* Improved automatic id generation selection in case of duplicate ids in - parametrize. - Thanks `@palaviv`_ for the complete PR (`#1474`_). - -* Now pytest warnings summary is shown up by default. Added a new flag - ``--disable-pytest-warnings`` to explicitly disable the warnings summary (`#1668`_). - -* Make ImportError during collection more explicit by reminding - the user to check the name of the test module/package(s) (`#1426`_). - Thanks `@omarkohl`_ for the complete PR (`#1520`_). - -* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks - `@mikofski`_ for the report and `@tomviner`_ for the PR (`#1544`_). - -* ``pytest.raises`` in the context manager form accepts a custom - ``message`` to raise when no exception occurred. - Thanks `@palaviv`_ for the complete PR (`#1616`_). - -* ``conftest.py`` files now benefit from assertion rewriting; previously it - was only available for test modules. Thanks `@flub`_, `@sober7`_ and - `@nicoddemus`_ for the PR (`#1619`_). - -* Text documents without any doctests no longer appear as "skipped". - Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). - -* Ensure that a module within a namespace package can be found when it - is specified on the command line together with the ``--pyargs`` - option. Thanks to `@taschini`_ for the PR (`#1597`_). - -* Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding - sub-expressions that happened to be ``False``, assuming this was redundant information. - Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and - `@tomviner`_ for the PR. - -* ``OptionGroup.addoption()`` now checks if option names were already - added before, to make it easier to track down issues like `#1618`_. - Before, you only got exceptions later from ``argparse`` library, - giving no clue about the actual reason for double-added options. - -* ``yield``-based tests are considered deprecated and will be removed in pytest-4.0. - Thanks `@nicoddemus`_ for the PR. - -* ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]`` - to avoid conflicts with other distutils commands (see `#567`_). ``[pytest]`` sections in - ``pytest.ini`` or ``tox.ini`` files are supported and unchanged. - Thanks `@nicoddemus`_ for the PR. - -* Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be - removed in pytest-4.0 (`#1684`_). - Thanks `@nicoddemus`_ for the PR. - -* Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled - for removal in pytest-4.0. It is recommended to pass a list of arguments instead (`#1723`_). - -* Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is - still present but is now considered deprecated. Thanks to `@RedBeardCode`_ and `@tomviner`_ - for the PR (`#1626`_). - -* ``optparse`` type usage now triggers DeprecationWarnings (`#1740`_). - - -* ``optparse`` backward compatibility supports float/complex types (`#457`_). - -* Refined logic for determining the ``rootdir``, considering only valid - paths which fixes a number of issues: `#1594`_, `#1435`_ and `#1471`_. - Updated the documentation according to current behavior. Thanks to - `@blueyed`_, `@davehunt`_ and `@matthiasha`_ for the PR. - -* Always include full assertion explanation. The previous behaviour was hiding - sub-expressions that happened to be False, assuming this was redundant information. - Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and - `@tomviner`_ for PR. - -* Better message in case of not using parametrized variable (see `#1539`_). - Thanks to `@tramwaj29`_ for the PR. - -* Updated docstrings with a more uniform style. - -* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown. - Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@JonathonSonesen`_ and - `@tomviner`_ for the PR. - -* No longer display the incorrect test deselection reason (`#1372`_). - Thanks `@ronnypfannschmidt`_ for the PR. - -* The ``--resultlog`` command line option has been deprecated: it is little used - and there are more modern and better alternatives (see `#830`_). - Thanks `@nicoddemus`_ for the PR. - -* Improve error message with fixture lookup errors: add an 'E' to the first - line and '>' to the rest. Fixes `#717`_. Thanks `@blueyed`_ for reporting and - a PR, `@eolo999`_ for the initial PR and `@tomviner`_ for his guidance during - EuroPython2016 sprint. - - -**Bug Fixes** - -* Parametrize now correctly handles duplicated test ids. - -* Fix internal error issue when the ``method`` argument is missing for - ``teardown_method()`` (`#1605`_). - -* Fix exception visualization in case the current working directory (CWD) gets - deleted during testing (`#1235`_). Thanks `@bukzor`_ for reporting. PR by - `@marscher`_. - -* Improve test output for logical expression with brackets (`#925`_). - Thanks `@DRMacIver`_ for reporting and `@RedBeardCode`_ for the PR. - -* Create correct diff for strings ending with newlines (`#1553`_). - Thanks `@Vogtinator`_ for reporting and `@RedBeardCode`_ and - `@tomviner`_ for the PR. - -* ``ConftestImportFailure`` now shows the traceback making it easier to - identify bugs in ``conftest.py`` files (`#1516`_). Thanks `@txomon`_ for - the PR. - -* Text documents without any doctests no longer appear as "skipped". - Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). - -* Fixed collection of classes with custom ``__new__`` method. - Fixes `#1579`_. Thanks to `@Stranger6667`_ for the PR. - -* Fixed scope overriding inside metafunc.parametrize (`#634`_). - Thanks to `@Stranger6667`_ for the PR. - -* Fixed the total tests tally in junit xml output (`#1798`_). - Thanks to `@cryporchild`_ for the PR. - -* Fixed off-by-one error with lines from ``request.node.warn``. - Thanks to `@blueyed`_ for the PR. - - -.. _#1210: https://github.com/pytest-dev/pytest/issues/1210 -.. _#1235: https://github.com/pytest-dev/pytest/issues/1235 -.. _#1351: https://github.com/pytest-dev/pytest/issues/1351 -.. _#1372: https://github.com/pytest-dev/pytest/issues/1372 -.. _#1421: https://github.com/pytest-dev/pytest/issues/1421 -.. _#1426: https://github.com/pytest-dev/pytest/issues/1426 -.. _#1428: https://github.com/pytest-dev/pytest/pull/1428 -.. _#1435: https://github.com/pytest-dev/pytest/issues/1435 -.. _#1441: https://github.com/pytest-dev/pytest/pull/1441 -.. _#1444: https://github.com/pytest-dev/pytest/pull/1444 -.. _#1454: https://github.com/pytest-dev/pytest/pull/1454 -.. _#1461: https://github.com/pytest-dev/pytest/pull/1461 -.. _#1468: https://github.com/pytest-dev/pytest/pull/1468 -.. _#1471: https://github.com/pytest-dev/pytest/issues/1471 -.. _#1474: https://github.com/pytest-dev/pytest/pull/1474 -.. _#1479: https://github.com/pytest-dev/pytest/issues/1479 -.. _#1502: https://github.com/pytest-dev/pytest/pull/1502 -.. _#1503: https://github.com/pytest-dev/pytest/issues/1503 -.. _#1516: https://github.com/pytest-dev/pytest/pull/1516 -.. _#1519: https://github.com/pytest-dev/pytest/pull/1519 -.. _#1520: https://github.com/pytest-dev/pytest/pull/1520 -.. _#1526: https://github.com/pytest-dev/pytest/pull/1526 -.. _#1539: https://github.com/pytest-dev/pytest/issues/1539 -.. _#1544: https://github.com/pytest-dev/pytest/issues/1544 -.. _#1546: https://github.com/pytest-dev/pytest/issues/1546 -.. _#1553: https://github.com/pytest-dev/pytest/issues/1553 -.. _#1562: https://github.com/pytest-dev/pytest/issues/1562 -.. _#1579: https://github.com/pytest-dev/pytest/issues/1579 -.. _#1580: https://github.com/pytest-dev/pytest/pull/1580 -.. _#1594: https://github.com/pytest-dev/pytest/issues/1594 -.. _#1597: https://github.com/pytest-dev/pytest/pull/1597 -.. _#1605: https://github.com/pytest-dev/pytest/issues/1605 -.. _#1616: https://github.com/pytest-dev/pytest/pull/1616 -.. _#1618: https://github.com/pytest-dev/pytest/issues/1618 -.. _#1619: https://github.com/pytest-dev/pytest/issues/1619 -.. _#1626: https://github.com/pytest-dev/pytest/pull/1626 -.. _#1627: https://github.com/pytest-dev/pytest/pull/1627 -.. _#1628: https://github.com/pytest-dev/pytest/pull/1628 -.. _#1629: https://github.com/pytest-dev/pytest/issues/1629 -.. _#1632: https://github.com/pytest-dev/pytest/issues/1632 -.. _#1633: https://github.com/pytest-dev/pytest/pull/1633 -.. _#1664: https://github.com/pytest-dev/pytest/pull/1664 -.. _#1668: https://github.com/pytest-dev/pytest/issues/1668 -.. _#1684: https://github.com/pytest-dev/pytest/pull/1684 -.. _#1723: https://github.com/pytest-dev/pytest/pull/1723 -.. _#1740: https://github.com/pytest-dev/pytest/issues/1740 -.. _#1749: https://github.com/pytest-dev/pytest/issues/1749 -.. _#1778: https://github.com/pytest-dev/pytest/pull/1778 -.. _#1795: https://github.com/pytest-dev/pytest/pull/1795 -.. _#1798: https://github.com/pytest-dev/pytest/pull/1798 -.. _#1809: https://github.com/pytest-dev/pytest/pull/1809 -.. _#372: https://github.com/pytest-dev/pytest/issues/372 -.. _#457: https://github.com/pytest-dev/pytest/issues/457 -.. _#460: https://github.com/pytest-dev/pytest/pull/460 -.. _#567: https://github.com/pytest-dev/pytest/pull/567 -.. _#607: https://github.com/pytest-dev/pytest/issues/607 -.. _#634: https://github.com/pytest-dev/pytest/issues/634 -.. _#717: https://github.com/pytest-dev/pytest/issues/717 -.. _#830: https://github.com/pytest-dev/pytest/issues/830 -.. _#925: https://github.com/pytest-dev/pytest/issues/925 - - -.. _@anntzer: https://github.com/anntzer -.. _@bagerard: https://github.com/bagerard -.. _@BeyondEvil: https://github.com/BeyondEvil -.. _@blueyed: https://github.com/blueyed -.. _@ceridwen: https://github.com/ceridwen -.. _@cryporchild: https://github.com/cryporchild -.. _@csaftoiu: https://github.com/csaftoiu -.. _@d6e: https://github.com/d6e -.. _@davehunt: https://github.com/davehunt -.. _@DRMacIver: https://github.com/DRMacIver -.. _@eolo999: https://github.com/eolo999 -.. _@fengxx: https://github.com/fengxx -.. _@flub: https://github.com/flub -.. _@gprasad84: https://github.com/gprasad84 -.. _@graingert: https://github.com/graingert -.. _@hartym: https://github.com/hartym -.. _@JonathonSonesen: https://github.com/JonathonSonesen -.. _@kalekundert: https://github.com/kalekundert -.. _@kvas-it: https://github.com/kvas-it -.. _@marscher: https://github.com/marscher -.. _@mikofski: https://github.com/mikofski -.. _@milliams: https://github.com/milliams -.. _@nikratio: https://github.com/nikratio -.. _@novas0x2a: https://github.com/novas0x2a -.. _@obestwalter: https://github.com/obestwalter -.. _@okken: https://github.com/okken -.. _@olegpidsadnyi: https://github.com/olegpidsadnyi -.. _@omarkohl: https://github.com/omarkohl -.. _@palaviv: https://github.com/palaviv -.. _@RedBeardCode: https://github.com/RedBeardCode -.. _@sallner: https://github.com/sallner -.. _@sober7: https://github.com/sober7 -.. _@Stranger6667: https://github.com/Stranger6667 -.. _@suzaku: https://github.com/suzaku -.. _@tareqalayan: https://github.com/tareqalayan -.. _@taschini: https://github.com/taschini -.. _@tramwaj29: https://github.com/tramwaj29 -.. _@txomon: https://github.com/txomon -.. _@Vogtinator: https://github.com/Vogtinator -.. _@matthiasha: https://github.com/matthiasha - - -2.9.2 (2016-05-31) -================== - -**Bug Fixes** - -* fix `#510`_: skip tests where one parameterize dimension was empty - thanks Alex Stapleton for the Report and `@RonnyPfannschmidt`_ for the PR - -* Fix Xfail does not work with condition keyword argument. - Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_ - for PR the (`#1524`_). - -* Fix win32 path issue when putting custom config file with absolute path - in ``pytest.main("-c your_absolute_path")``. - -* Fix maximum recursion depth detection when raised error class is not aware - of unicode/encoded bytes. - Thanks `@prusse-martin`_ for the PR (`#1506`_). - -* Fix ``pytest.mark.skip`` mark when used in strict mode. - Thanks `@pquentin`_ for the PR and `@RonnyPfannschmidt`_ for - showing how to fix the bug. - -* Minor improvements and fixes to the documentation. - Thanks `@omarkohl`_ for the PR. - -* Fix ``--fixtures`` to show all fixture definitions as opposed to just - one per fixture name. - Thanks to `@hackebrot`_ for the PR. - -.. _#510: https://github.com/pytest-dev/pytest/issues/510 -.. _#1506: https://github.com/pytest-dev/pytest/pull/1506 -.. _#1496: https://github.com/pytest-dev/pytest/issues/1496 -.. _#1524: https://github.com/pytest-dev/pytest/pull/1524 - -.. _@prusse-martin: https://github.com/prusse-martin -.. _@astraw38: https://github.com/astraw38 - - -2.9.1 (2016-03-17) -================== - -**Bug Fixes** - -* Improve error message when a plugin fails to load. - Thanks `@nicoddemus`_ for the PR. - -* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_): - ``pytest.fail`` with non-ascii characters raises an internal pytest error. - Thanks `@nicoddemus`_ for the PR. - -* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs - contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_). - -* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors - containing non-ascii lines at the point of failure generated an internal - py.test error. - Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR. - -* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize - attempt to decode it as utf-8 ignoring errors. - -* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line. - -* Fix (`#138`_): better reporting for python 3.3+ chained exceptions - -.. _#1437: https://github.com/pytest-dev/pytest/issues/1437 -.. _#469: https://github.com/pytest-dev/pytest/issues/469 -.. _#1431: https://github.com/pytest-dev/pytest/pull/1431 -.. _#649: https://github.com/pytest-dev/pytest/issues/649 -.. _#138: https://github.com/pytest-dev/pytest/issues/138 - -.. _@asottile: https://github.com/asottile - - -2.9.0 (2016-02-29) -================== - -**New Features** - -* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests. - Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_). - -* ``--doctest-glob`` may now be passed multiple times in the command-line. - Thanks `@jab`_ and `@nicoddemus`_ for the PR. - -* New ``-rp`` and ``-rP`` reporting options give the summary and full output - of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR. - -* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS`` - tests to fail the test suite (defaulting to ``False``). There's also a - ``xfail_strict`` ini option that can be used to configure it project-wise. - Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_). - -* ``Parser.addini`` now supports options of type ``bool``. - Thanks `@nicoddemus`_ for the PR. - -* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings - in doctest output (similar to ``ALLOW_UNICODE``). - Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_). - -* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors. - Fixes `#1366`_. - Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR. - -* Catch ``IndexError`` exceptions when getting exception source location. - Fixes a pytest internal error for dynamically generated code (fixtures and tests) - where source lines are fake by intention. - -**Changes** - -* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been - merged into the ``pytest`` repository as ``pytest._code``. This decision - was made because ``py.code`` had very few uses outside ``pytest`` and the - fact that it was in a different repository made it difficult to fix bugs on - its code in a timely manner. The team hopes with this to be able to better - refactor out and improve that code. - This change shouldn't affect users, but it is useful to let users aware - if they encounter any strange behavior. - - Keep in mind that the code for ``pytest._code`` is **private** and - **experimental**, so you definitely should not import it explicitly! - - Please note that the original ``py.code`` is still available in - `pylib <https://pylib.readthedocs.io>`_. - -* ``pytest_enter_pdb`` now optionally receives the pytest config object. - Thanks `@nicoddemus`_ for the PR. - -* Removed code and documentation for Python 2.5 or lower versions, - including removal of the obsolete ``_pytest.assertion.oldinterpret`` module. - Thanks `@nicoddemus`_ for the PR (`#1226`_). - -* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is - found in the environment, even when ``-vv`` isn't used. - Thanks `@The-Compiler`_ for the PR. - -* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and - ``--failed-first`` respectively. - Thanks `@MichaelAquilina`_ for the PR. - -* Added expected exceptions to ``pytest.raises`` fail message. - -* Collection only displays progress ("collecting X items") when in a terminal. - This avoids cluttering the output when using ``--color=yes`` to obtain - colors in CI integrations systems (`#1397`_). - -**Bug Fixes** - -* The ``-s`` and ``-c`` options should now work under ``xdist``; - ``Config.fromdictargs`` now represents its input much more faithfully. - Thanks to `@bukzor`_ for the complete PR (`#680`_). - -* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting. - Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR. - -* Fix formatting utf-8 explanation messages (`#1379`_). - Thanks `@biern`_ for the PR. - -* Fix `traceback style docs`_ to describe all of the available options - (auto/long/short/line/native/no), with ``auto`` being the default since v2.6. - Thanks `@hackebrot`_ for the PR. - -* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records - with same name. - -.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing - -.. _#1609: https://github.com/pytest-dev/pytest/issues/1609 -.. _#1422: https://github.com/pytest-dev/pytest/issues/1422 -.. _#1379: https://github.com/pytest-dev/pytest/issues/1379 -.. _#1366: https://github.com/pytest-dev/pytest/issues/1366 -.. _#1040: https://github.com/pytest-dev/pytest/pull/1040 -.. _#680: https://github.com/pytest-dev/pytest/issues/680 -.. _#1287: https://github.com/pytest-dev/pytest/pull/1287 -.. _#1226: https://github.com/pytest-dev/pytest/pull/1226 -.. _#1290: https://github.com/pytest-dev/pytest/pull/1290 -.. _#1355: https://github.com/pytest-dev/pytest/pull/1355 -.. _#1397: https://github.com/pytest-dev/pytest/issues/1397 -.. _@biern: https://github.com/biern -.. _@MichaelAquilina: https://github.com/MichaelAquilina -.. _@bukzor: https://github.com/bukzor -.. _@hpk42: https://github.com/hpk42 -.. _@nicoddemus: https://github.com/nicoddemus -.. _@jab: https://github.com/jab -.. _@codewarrior0: https://github.com/codewarrior0 -.. _@jaraco: https://github.com/jaraco -.. _@The-Compiler: https://github.com/The-Compiler -.. _@Shinkenjoe: https://github.com/Shinkenjoe -.. _@tomviner: https://github.com/tomviner -.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt -.. _@rabbbit: https://github.com/rabbbit -.. _@hackebrot: https://github.com/hackebrot -.. _@pquentin: https://github.com/pquentin -.. _@ioggstream: https://github.com/ioggstream - -2.8.7 (2016-01-24) -================== - -- fix #1338: use predictable object resolution for monkeypatch - -2.8.6 (2016-01-21) -================== - -- fix #1259: allow for double nodeids in junitxml, - this was a regression failing plugins combinations - like pytest-pep8 + pytest-flakes - -- Workaround for exception that occurs in pyreadline when using - ``--pdb`` with standard I/O capture enabled. - Thanks Erik M. Bray for the PR. - -- fix #900: Better error message in case the target of a ``monkeypatch`` call - raises an ``ImportError``. - -- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1). - Thanks David R. MacIver for the report and Bruno Oliveira for the PR. - -- fix #1223: captured stdout and stderr are now properly displayed before - entering pdb when ``--pdb`` is used instead of being thrown away. - Thanks Cal Leeming for the PR. - -- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now - properly displayed. - Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR. - -- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode. - Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR. - -- fix #1334: Add captured stdout to jUnit XML report on setup error. - Thanks Georgy Dyuldin for the PR. - - -2.8.5 (2015-12-11) -================== - -- fix #1243: fixed issue where class attributes injected during collection could break pytest. - PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help. - -- fix #1074: precompute junitxml chunks instead of storing the whole tree in objects - Thanks Bruno Oliveira for the report and Ronny Pfannschmidt for the PR - -- fix #1238: fix ``pytest.deprecated_call()`` receiving multiple arguments - (Regression introduced in 2.8.4). Thanks Alex Gaynor for the report and - Bruno Oliveira for the PR. - - -2.8.4 (2015-12-06) -================== - -- fix #1190: ``deprecated_call()`` now works when the deprecated - function has been already called by another test in the same - module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the - PR. - -- fix #1198: ``--pastebin`` option now works on Python 3. Thanks - Mehdy Khoshnoody for the PR. - -- fix #1219: ``--pastebin`` now works correctly when captured output contains - non-ascii characters. Thanks Bruno Oliveira for the PR. - -- fix #1204: another error when collecting with a nasty __getattr__(). - Thanks Florian Bruhin for the PR. - -- fix the summary printed when no tests did run. - Thanks Florian Bruhin for the PR. -- fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist - -- a number of documentation modernizations wrt good practices. - Thanks Bruno Oliveira for the PR. - -2.8.3 (2015-11-18) -================== - -- fix #1169: add __name__ attribute to testcases in TestCaseFunction to - support the @unittest.skip decorator on functions and methods. - Thanks Lee Kamentsky for the PR. - -- fix #1035: collecting tests if test module level obj has __getattr__(). - Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR. - -- fix #331: don't collect tests if their failure cannot be reported correctly - e.g. they are a callable instance of a class. - -- fix #1133: fixed internal error when filtering tracebacks where one entry - belongs to a file which is no longer available. - Thanks Bruno Oliveira for the PR. - -- enhancement made to highlight in red the name of the failing tests so - they stand out in the output. - Thanks Gabriel Reis for the PR. - -- add more talks to the documentation -- extend documentation on the --ignore cli option -- use pytest-runner for setuptools integration -- minor fixes for interaction with OS X El Capitan - system integrity protection (thanks Florian) - - -2.8.2 (2015-10-07) -================== - -- fix #1085: proper handling of encoding errors when passing encoded byte - strings to pytest.parametrize in Python 2. - Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR. - -- fix #1087: handling SystemError when passing empty byte strings to - pytest.parametrize in Python 3. - Thanks Paul Kehrer for the report and Bruno Oliveira for the PR. - -- fix #995: fixed internal error when filtering tracebacks where one entry - was generated by an exec() statement. - Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko - for contributing and Bruno Oliveira for the PR. - -- fix #1100 and #1057: errors when using autouse fixtures and doctest modules. - Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno - Oliveira for the PR. - -2.8.1 (2015-09-29) -================== - -- fix #1034: Add missing nodeid on pytest_logwarning call in - addhook. Thanks Simon Gomizelj for the PR. - -- 'deprecated_call' is now only satisfied with a DeprecationWarning or - PendingDeprecationWarning. Before 2.8.0, it accepted any warning, and 2.8.0 - made it accept only DeprecationWarning (but not PendingDeprecationWarning). - Thanks Alex Gaynor for the issue and Eric Hunsberger for the PR. - -- fix issue #1073: avoid calling __getattr__ on potential plugin objects. - This fixes an incompatibility with pytest-django. Thanks Andreas Pelme, - Bruno Oliveira and Ronny Pfannschmidt for contributing and Holger Krekel - for the fix. - -- Fix issue #704: handle versionconflict during plugin loading more - gracefully. Thanks Bruno Oliveira for the PR. - -- Fix issue #1064: ""--junitxml" regression when used with the - "pytest-xdist" plugin, with test reports being assigned to the wrong tests. - Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR. - -- (experimental) adapt more SEMVER style versioning and change meaning of - master branch in git repo: "master" branch now keeps the bugfixes, changes - aimed for micro releases. "features" branch will only be released - with minor or major pytest releases. - -- Fix issue #766 by removing documentation references to distutils. - Thanks Russel Winder. - -- Fix issue #1030: now byte-strings are escaped to produce item node ids - to make them always serializable. - Thanks Andy Freeland for the report and Bruno Oliveira for the PR. - -- Python 2: if unicode parametrized values are convertible to ascii, their - ascii representation is used for the node id. - -- Fix issue #411: Add __eq__ method to assertion comparison example. - Thanks Ben Webb. -- Fix issue #653: deprecated_call can be used as context manager. - -- fix issue 877: properly handle assertion explanations with non-ascii repr - Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR. - -- fix issue 1029: transform errors when writing cache values into pytest-warnings - -2.8.0 (2015-09-18) -================== - -- new ``--lf`` and ``-ff`` options to run only the last failing tests or - "failing tests first" from the last run. This functionality is provided - through porting the formerly external pytest-cache plugin into pytest core. - BACKWARD INCOMPAT: if you used pytest-cache's functionality to persist - data between test runs be aware that we don't serialize sets anymore. - Thanks Ronny Pfannschmidt for most of the merging work. - -- "-r" option now accepts "a" to include all possible reports, similar - to passing "fEsxXw" explicitly (isse960). - Thanks Abhijeet Kasurde for the PR. - -- avoid python3.5 deprecation warnings by introducing version - specific inspection helpers, thanks Michael Droettboom. - -- fix issue562: @nose.tools.istest now fully respected. - -- fix issue934: when string comparison fails and a diff is too large to display - without passing -vv, still show a few lines of the diff. - Thanks Florian Bruhin for the report and Bruno Oliveira for the PR. - -- fix issue736: Fix a bug where fixture params would be discarded when combined - with parametrization markers. - Thanks to Markus Unterwaditzer for the PR. - -- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the - ``u`` prefix is stripped from unicode strings in expected doctest output. This - allows doctests which use unicode to run in Python 2 and 3 unchanged. - Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR. - -- parametrize now also generates meaningful test IDs for enum, regex and class - objects (as opposed to class instances). - Thanks to Florian Bruhin for the PR. - -- Add 'warns' to assert that warnings are thrown (like 'raises'). - Thanks to Eric Hunsberger for the PR. - -- Fix issue683: Do not apply an already applied mark. Thanks ojake for the PR. - -- Deal with capturing failures better so fewer exceptions get lost to - /dev/null. Thanks David Szotten for the PR. - -- fix issue730: deprecate and warn about the --genscript option. - Thanks Ronny Pfannschmidt for the report and Christian Pommranz for the PR. - -- fix issue751: multiple parametrize with ids bug if it parametrizes class with - two or more test methods. Thanks Sergey Chipiga for reporting and Jan - Bednarik for PR. - -- fix issue82: avoid loading conftest files from setup.cfg/pytest.ini/tox.ini - files and upwards by default (--confcutdir can still be set to override this). - Thanks Bruno Oliveira for the PR. - -- fix issue768: docstrings found in python modules were not setting up session - fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR. - -- added ``tmpdir_factory``, a session-scoped fixture that can be used to create - directories under the base temporary directory. Previously this object was - installed as a ``_tmpdirhandler`` attribute of the ``config`` object, but now it - is part of the official API and using ``config._tmpdirhandler`` is - deprecated. - Thanks Bruno Oliveira for the PR. - -- fix issue808: pytest's internal assertion rewrite hook now implements the - optional PEP302 get_data API so tests can access data files next to them. - Thanks xmo-odoo for request and example and Bruno Oliveira for - the PR. - -- rootdir and inifile are now displayed during usage errors to help - users diagnose problems such as unexpected ini files which add - unknown options being picked up by pytest. Thanks to Pavel Savchenko for - bringing the problem to attention in #821 and Bruno Oliveira for the PR. - -- Summary bar now is colored yellow for warning - situations such as: all tests either were skipped or xpass/xfailed, - or no tests were run at all (this is a partial fix for issue500). - -- fix issue812: pytest now exits with status code 5 in situations where no - tests were run at all, such as the directory given in the command line does - not contain any tests or as result of a command line option filters - all out all tests (-k for example). - Thanks Eric Siegerman (issue812) and Bruno Oliveira for the PR. - -- Summary bar now is colored yellow for warning - situations such as: all tests either were skipped or xpass/xfailed, - or no tests were run at all (related to issue500). - Thanks Eric Siegerman. - -- New ``testpaths`` ini option: list of directories to search for tests - when executing pytest from the root directory. This can be used - to speed up test collection when a project has well specified directories - for tests, being usually more practical than configuring norecursedirs for - all directories that do not contain tests. - Thanks to Adrian for idea (#694) and Bruno Oliveira for the PR. - -- fix issue713: JUnit XML reports for doctest failures. - Thanks Punyashloka Biswal. - -- fix issue970: internal pytest warnings now appear as "pytest-warnings" in - the terminal instead of "warnings", so it is clear for users that those - warnings are from pytest and not from the builtin "warnings" module. - Thanks Bruno Oliveira. - -- Include setup and teardown in junitxml test durations. - Thanks Janne Vanhala. - -- fix issue735: assertion failures on debug versions of Python 3.4+ - -- new option ``--import-mode`` to allow to change test module importing - behaviour to append to sys.path instead of prepending. This better allows - to run test modules against installed versions of a package even if the - package under test has the same import root. In this example:: - - testing/__init__.py - testing/test_pkg_under_test.py - pkg_under_test/ - - the tests will run against the installed version - of pkg_under_test when ``--import-mode=append`` is used whereas - by default they would always pick up the local version. Thanks Holger Krekel. - -- pytester: add method ``TmpTestdir.delete_loaded_modules()``, and call it - from ``inline_run()`` to allow temporary modules to be reloaded. - Thanks Eduardo Schettino. - -- internally refactor pluginmanager API and code so that there - is a clear distinction between a pytest-agnostic rather simple - pluginmanager and the PytestPluginManager which adds a lot of - behaviour, among it handling of the local conftest files. - In terms of documented methods this is a backward compatible - change but it might still break 3rd party plugins which relied on - details like especially the pluginmanager.add_shutdown() API. - Thanks Holger Krekel. - -- pluginmanagement: introduce ``pytest.hookimpl`` and - ``pytest.hookspec`` decorators for setting impl/spec - specific parameters. This substitutes the previous - now deprecated use of ``pytest.mark`` which is meant to - contain markers for test functions only. - -- write/refine docs for "writing plugins" which now have their - own page and are separate from the "using/installing plugins`` page. - -- fix issue732: properly unregister plugins from any hook calling - sites allowing to have temporary plugins during test execution. - -- deprecate and warn about ``__multicall__`` argument in hook - implementations. Use the ``hookwrapper`` mechanism instead already - introduced with pytest-2.7. - -- speed up pytest's own test suite considerably by using inprocess - tests by default (testrun can be modified with --runpytest=subprocess - to create subprocesses in many places instead). The main - APIs to run pytest in a test is "runpytest()" or "runpytest_subprocess" - and "runpytest_inprocess" if you need a particular way of running - the test. In all cases you get back a RunResult but the inprocess - one will also have a "reprec" attribute with the recorded events/reports. - -- fix monkeypatch.setattr("x.y", raising=False) to actually not raise - if "y" is not a pre-existing attribute. Thanks Florian Bruhin. - -- fix issue741: make running output from testdir.run copy/pasteable - Thanks Bruno Oliveira. - -- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files. - -- add ``file`` and ``line`` attributes to JUnit-XML output. - -- fix issue890: changed extension of all documentation files from ``txt`` to - ``rst``. Thanks to Abhijeet for the PR. - -- fix issue714: add ability to apply indirect=True parameter on particular argnames. - Thanks Elizaveta239. - -- fix issue890: changed extension of all documentation files from ``txt`` to - ``rst``. Thanks to Abhijeet for the PR. - -- fix issue957: "# doctest: SKIP" option will now register doctests as SKIPPED - rather than PASSED. - Thanks Thomas Grainger for the report and Bruno Oliveira for the PR. - -- issue951: add new record_xml_property fixture, that supports logging - additional information on xml output. Thanks David Diaz for the PR. - -- issue949: paths after normal options (for example ``-s``, ``-v``, etc) are now - properly used to discover ``rootdir`` and ``ini`` files. - Thanks Peter Lauri for the report and Bruno Oliveira for the PR. - -2.7.3 (2015-09-15) -================== - -- Allow 'dev', 'rc', or other non-integer version strings in ``importorskip``. - Thanks to Eric Hunsberger for the PR. - -- fix issue856: consider --color parameter in all outputs (for example - --fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR. - -- fix issue855: passing str objects as ``plugins`` argument to pytest.main - is now interpreted as a module name to be imported and registered as a - plugin, instead of silently having no effect. - Thanks xmo-odoo for the report and Bruno Oliveira for the PR. - -- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks - Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and - Thomas Kluyver. - -- fix issue842: applying markers in classes no longer propagate this markers - to superclasses which also have markers. - Thanks xmo-odoo for the report and Bruno Oliveira for the PR. - -- preserve warning functions after call to pytest.deprecated_call. Thanks - Pieter Mulder for PR. - -- fix issue854: autouse yield_fixtures defined as class members of - unittest.TestCase subclasses now work as expected. - Thannks xmo-odoo for the report and Bruno Oliveira for the PR. - -- fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the - fixtures declared on the first one. - Thanks Florian Bruhin for reporting and Bruno Oliveira for the PR. - -- fix issue863: skipped tests now report the correct reason when a skip/xfail - condition is met when using multiple markers. - Thanks Raphael Pierzina for reporting and Bruno Oliveira for the PR. - -- optimized tmpdir fixture initialization, which should make test sessions - faster (specially when using pytest-xdist). The only visible effect - is that now pytest uses a subdirectory in the $TEMP directory for all - directories created by this fixture (defaults to $TEMP/pytest-$USER). - Thanks Bruno Oliveira for the PR. - -2.7.2 (2015-06-23) -================== - -- fix issue767: pytest.raises value attribute does not contain the exception - instance on Python 2.6. Thanks Eric Siegerman for providing the test - case and Bruno Oliveira for PR. - -- Automatically create directory for junitxml and results log. - Thanks Aron Curzon. - -- fix issue713: JUnit XML reports for doctest failures. - Thanks Punyashloka Biswal. - -- fix issue735: assertion failures on debug versions of Python 3.4+ - Thanks Benjamin Peterson. - -- fix issue114: skipif marker reports to internal skipping plugin; - Thanks Floris Bruynooghe for reporting and Bruno Oliveira for the PR. - -- fix issue748: unittest.SkipTest reports to internal pytest unittest plugin. - Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR. - -- fix issue718: failed to create representation of sets containing unsortable - elements in python 2. Thanks Edison Gustavo Muenz. - -- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29 - which has a refined algorithm for traceback generation. - - -2.7.1 (2015-05-19) -================== - -- fix issue731: do not get confused by the braces which may be present - and unbalanced in an object's repr while collapsing False - explanations. Thanks Carl Meyer for the report and test case. - -- fix issue553: properly handling inspect.getsourcelines failures in - FixtureLookupError which would lead to an internal error, - obfuscating the original problem. Thanks talljosh for initial - diagnose/patch and Bruno Oliveira for final patch. - -- fix issue660: properly report scope-mismatch-access errors - independently from ordering of fixture arguments. Also - avoid the pytest internal traceback which does not provide - information to the user. Thanks Holger Krekel. - -- streamlined and documented release process. Also all versions - (in setup.py and documentation generation) are now read - from _pytest/__init__.py. Thanks Holger Krekel. - -- fixed docs to remove the notion that yield-fixtures are experimental. - They are here to stay :) Thanks Bruno Oliveira. - -- Support building wheels by using environment markers for the - requirements. Thanks Ionel Maries Cristian. - -- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing - when tests raised SystemExit. Thanks Holger Krekel. - -- reintroduced _pytest fixture of the pytester plugin which is used - at least by pytest-xdist. - -2.7.0 (2015-03-26) -================== - -- fix issue435: make reload() work when assert rewriting is active. - Thanks Daniel Hahler. - -- fix issue616: conftest.py files and their contained fixutres are now - properly considered for visibility, independently from the exact - current working directory and test arguments that are used. - Many thanks to Eric Siegerman and his PR235 which contains - systematic tests for conftest visibility and now passes. - This change also introduces the concept of a ``rootdir`` which - is printed as a new pytest header and documented in the pytest - customize web page. - -- change reporting of "diverted" tests, i.e. tests that are collected - in one file but actually come from another (e.g. when tests in a test class - come from a base class in a different file). We now show the nodeid - and indicate via a postfix the other file. - -- add ability to set command line options by environment variable PYTEST_ADDOPTS. - -- added documentation on the new pytest-dev teams on bitbucket and - github. See https://pytest.org/latest/contributing.html . - Thanks to Anatoly for pushing and initial work on this. - -- fix issue650: new option ``--docttest-ignore-import-errors`` which - will turn import errors in doctests into skips. Thanks Charles Cloud - for the complete PR. - -- fix issue655: work around different ways that cause python2/3 - to leak sys.exc_info into fixtures/tests causing failures in 3rd party code - -- fix issue615: assertion rewriting did not correctly escape % signs - when formatting boolean operations, which tripped over mixing - booleans with modulo operators. Thanks to Tom Viner for the report, - triaging and fix. - -- implement issue351: add ability to specify parametrize ids as a callable - to generate custom test ids. Thanks Brianna Laugher for the idea and - implementation. - -- introduce and document new hookwrapper mechanism useful for plugins - which want to wrap the execution of certain hooks for their purposes. - This supersedes the undocumented ``__multicall__`` protocol which - pytest itself and some external plugins use. Note that pytest-2.8 - is scheduled to drop supporting the old ``__multicall__`` - and only support the hookwrapper protocol. - -- majorly speed up invocation of plugin hooks - -- use hookwrapper mechanism in builtin pytest plugins. - -- add a doctest ini option for doctest flags, thanks Holger Peters. - -- add note to docs that if you want to mark a parameter and the - parameter is a callable, you also need to pass in a reason to disambiguate - it from the "decorator" case. Thanks Tom Viner. - -- "python_classes" and "python_functions" options now support glob-patterns - for test discovery, as discussed in issue600. Thanks Ldiary Translations. - -- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff). - -- fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise). - -- On failure, the ``sys.last_value``, ``sys.last_type`` and - ``sys.last_traceback`` are set, so that a user can inspect the error - via postmortem debugging (almarklein). - -2.6.4 (2014-10-24) -================== - -- Improve assertion failure reporting on iterables, by using ndiff and - pprint. - -- removed outdated japanese docs from source tree. - -- docs for "pytest_addhooks" hook. Thanks Bruno Oliveira. - -- updated plugin index docs. Thanks Bruno Oliveira. - -- fix issue557: with "-k" we only allow the old style "-" for negation - at the beginning of strings and even that is deprecated. Use "not" instead. - This should allow to pick parametrized tests where "-" appeared in the parameter. - -- fix issue604: Escape % character in the assertion message. - -- fix issue620: add explanation in the --genscript target about what - the binary blob means. Thanks Dinu Gherman. - -- fix issue614: fixed pastebin support. - - -- fix issue620: add explanation in the --genscript target about what - the binary blob means. Thanks Dinu Gherman. - -- fix issue614: fixed pastebin support. - -2.6.3 (2014-09-24) -================== - -- fix issue575: xunit-xml was reporting collection errors as failures - instead of errors, thanks Oleg Sinyavskiy. - -- fix issue582: fix setuptools example, thanks Laszlo Papp and Ronny - Pfannschmidt. - -- Fix infinite recursion bug when pickling capture.EncodedFile, thanks - Uwe Schmitt. - -- fix issue589: fix bad interaction with numpy and others when showing - exceptions. Check for precise "maximum recursion depth exceed" exception - instead of presuming any RuntimeError is that one (implemented in py - dep). Thanks Charles Cloud for analysing the issue. - -- fix conftest related fixture visibility issue: when running with a - CWD outside of a test package pytest would get fixture discovery wrong. - Thanks to Wolfgang Schnerring for figuring out a reproducible example. - -- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the - timeout when interactively entering pdb). Thanks Wolfgang Schnerring. - -- check xfail/skip also with non-python function test items. Thanks - Floris Bruynooghe. - -2.6.2 (2014-09-05) -================== - -- Added function pytest.freeze_includes(), which makes it easy to embed - pytest into executables using tools like cx_freeze. - See docs for examples and rationale. Thanks Bruno Oliveira. - -- Improve assertion rewriting cache invalidation precision. - -- fixed issue561: adapt autouse fixture example for python3. - -- fixed issue453: assertion rewriting issue with __repr__ containing - "\n{", "\n}" and "\n~". - -- fix issue560: correctly display code if an "else:" or "finally:" is - followed by statements on the same line. - -- Fix example in monkeypatch documentation, thanks t-8ch. - -- fix issue572: correct tmpdir doc example for python3. - -- Do not mark as universal wheel because Python 2.6 is different from - other builds due to the extra argparse dependency. Fixes issue566. - Thanks sontek. - -- Implement issue549: user-provided assertion messages now no longer - replace the py.test introspection message but are shown in addition - to them. - -2.6.1 (2014-08-07) -================== - -- No longer show line numbers in the --verbose output, the output is now - purely the nodeid. The line number is still shown in failure reports. - Thanks Floris Bruynooghe. - -- fix issue437 where assertion rewriting could cause pytest-xdist slaves - to collect different tests. Thanks Bruno Oliveira. - -- fix issue555: add "errors" attribute to capture-streams to satisfy - some distutils and possibly other code accessing sys.stdout.errors. - -- fix issue547 capsys/capfd also work when output capturing ("-s") is disabled. - -- address issue170: allow pytest.mark.xfail(...) to specify expected exceptions via - an optional "raises=EXC" argument where EXC can be a single exception - or a tuple of exception classes. Thanks David Mohr for the complete - PR. - -- fix integration of pytest with unittest.mock.patch decorator when - it uses the "new" argument. Thanks Nicolas Delaby for test and PR. - -- fix issue with detecting conftest files if the arguments contain - "::" node id specifications (copy pasted from "-v" output) - -- fix issue544 by only removing "@NUM" at the end of "::" separated parts - and if the part has a ".py" extension - -- don't use py.std import helper, rather import things directly. - Thanks Bruno Oliveira. - -2.6 -=== - -- Cache exceptions from fixtures according to their scope (issue 467). - -- fix issue537: Avoid importing old assertion reinterpretation code by default. - -- fix issue364: shorten and enhance tracebacks representation by default. - The new "--tb=auto" option (default) will only display long tracebacks - for the first and last entry. You can get the old behaviour of printing - all entries as long entries with "--tb=long". Also short entries by - default are now printed very similarly to "--tb=native" ones. - -- fix issue514: teach assertion reinterpretation about private class attributes - -- change -v output to include full node IDs of tests. Users can copy - a node ID from a test run, including line number, and use it as a - positional argument in order to run only a single test. - -- fix issue 475: fail early and comprehensible if calling - pytest.raises with wrong exception type. - -- fix issue516: tell in getting-started about current dependencies. - -- cleanup setup.py a bit and specify supported versions. Thanks Jurko - Gospodnetic for the PR. - -- change XPASS colour to yellow rather then red when tests are run - with -v. - -- fix issue473: work around mock putting an unbound method into a class - dict when double-patching. - -- fix issue498: if a fixture finalizer fails, make sure that - the fixture is still invalidated. - -- fix issue453: the result of the pytest_assertrepr_compare hook now gets - it's newlines escaped so that format_exception does not blow up. - -- internal new warning system: pytest will now produce warnings when - it detects oddities in your test collection or execution. - Warnings are ultimately sent to a new pytest_logwarning hook which is - currently only implemented by the terminal plugin which displays - warnings in the summary line and shows more details when -rw (report on - warnings) is specified. - -- change skips into warnings for test classes with an __init__ and - callables in test modules which look like a test but are not functions. - -- fix issue436: improved finding of initial conftest files from command - line arguments by using the result of parse_known_args rather than - the previous flaky heuristics. Thanks Marc Abramowitz for tests - and initial fixing approaches in this area. - -- fix issue #479: properly handle nose/unittest(2) SkipTest exceptions - during collection/loading of test modules. Thanks to Marc Schlaich - for the complete PR. - -- fix issue490: include pytest_load_initial_conftests in documentation - and improve docstring. - -- fix issue472: clarify that ``pytest.config.getvalue()`` cannot work - if it's triggered ahead of command line parsing. - -- merge PR123: improved integration with mock.patch decorator on tests. - -- fix issue412: messing with stdout/stderr FD-level streams is now - captured without crashes. - -- fix issue483: trial/py33 works now properly. Thanks Daniel Grana for PR. - -- improve example for pytest integration with "python setup.py test" - which now has a generic "-a" or "--pytest-args" option where you - can pass additional options as a quoted string. Thanks Trevor Bekolay. - -- simplified internal capturing mechanism and made it more robust - against tests or setups changing FD1/FD2, also better integrated - now with pytest.pdb() in single tests. - -- improvements to pytest's own test-suite leakage detection, courtesy of PRs - from Marc Abramowitz - -- fix issue492: avoid leak in test_writeorg. Thanks Marc Abramowitz. - -- fix issue493: don't run tests in doc directory with ``python setup.py test`` - (use tox -e doctesting for that) - -- fix issue486: better reporting and handling of early conftest loading failures - -- some cleanup and simplification of internal conftest handling. - -- work a bit harder to break reference cycles when catching exceptions. - Thanks Jurko Gospodnetic. - -- fix issue443: fix skip examples to use proper comparison. Thanks Alex - Groenholm. - -- support nose-style ``__test__`` attribute on modules, classes and - functions, including unittest-style Classes. If set to False, the - test will not be collected. - -- fix issue512: show "<notset>" for arguments which might not be set - in monkeypatch plugin. Improves output in documentation. - - -2.5.2 (2014-01-29) -================== - -- fix issue409 -- better interoperate with cx_freeze by not - trying to import from collections.abc which causes problems - for py27/cx_freeze. Thanks Wolfgang L. for reporting and tracking it down. - -- fixed docs and code to use "pytest" instead of "py.test" almost everywhere. - Thanks Jurko Gospodnetic for the complete PR. - -- fix issue425: mention at end of "py.test -h" that --markers - and --fixtures work according to specified test path (or current dir) - -- fix issue413: exceptions with unicode attributes are now printed - correctly also on python2 and with pytest-xdist runs. (the fix - requires py-1.4.20) - -- copy, cleanup and integrate py.io capture - from pylib 1.4.20.dev2 (rev 13d9af95547e) - -- address issue416: clarify docs as to conftest.py loading semantics - -- fix issue429: comparing byte strings with non-ascii chars in assert - expressions now work better. Thanks Floris Bruynooghe. - -- make capfd/capsys.capture private, its unused and shouldn't be exposed - - -2.5.1 (2013-12-17) -================== - -- merge new documentation styling PR from Tobias Bieniek. - -- fix issue403: allow parametrize of multiple same-name functions within - a collection node. Thanks Andreas Kloeckner and Alex Gaynor for reporting - and analysis. - -- Allow parameterized fixtures to specify the ID of the parameters by - adding an ids argument to pytest.fixture() and pytest.yield_fixture(). - Thanks Floris Bruynooghe. - -- fix issue404 by always using the binary xml escape in the junitxml - plugin. Thanks Ronny Pfannschmidt. - -- fix issue407: fix addoption docstring to point to argparse instead of - optparse. Thanks Daniel D. Wright. - - - -2.5.0 (2013-12-12) -================== - -- dropped python2.5 from automated release testing of pytest itself - which means it's probably going to break soon (but still works - with this release we believe). - -- simplified and fixed implementation for calling finalizers when - parametrized fixtures or function arguments are involved. finalization - is now performed lazily at setup time instead of in the "teardown phase". - While this might sound odd at first, it helps to ensure that we are - correctly handling setup/teardown even in complex code. User-level code - should not be affected unless it's implementing the pytest_runtest_teardown - hook and expecting certain fixture instances are torn down within (very - unlikely and would have been unreliable anyway). - -- PR90: add --color=yes|no|auto option to force terminal coloring - mode ("auto" is default). Thanks Marc Abramowitz. - -- fix issue319 - correctly show unicode in assertion errors. Many - thanks to Floris Bruynooghe for the complete PR. Also means - we depend on py>=1.4.19 now. - -- fix issue396 - correctly sort and finalize class-scoped parametrized - tests independently from number of methods on the class. - -- refix issue323 in a better way -- parametrization should now never - cause Runtime Recursion errors because the underlying algorithm - for re-ordering tests per-scope/per-fixture is not recursive - anymore (it was tail-call recursive before which could lead - to problems for more than >966 non-function scoped parameters). - -- fix issue290 - there is preliminary support now for parametrizing - with repeated same values (sometimes useful to test if calling - a second time works as with the first time). - -- close issue240 - document precisely how pytest module importing - works, discuss the two common test directory layouts, and how it - interacts with PEP420-namespace packages. - -- fix issue246 fix finalizer order to be LIFO on independent fixtures - depending on a parametrized higher-than-function scoped fixture. - (was quite some effort so please bear with the complexity of this sentence :) - Thanks Ralph Schmitt for the precise failure example. - -- fix issue244 by implementing special index for parameters to only use - indices for paramentrized test ids - -- fix issue287 by running all finalizers but saving the exception - from the first failing finalizer and re-raising it so teardown will - still have failed. We reraise the first failing exception because - it might be the cause for other finalizers to fail. - -- fix ordering when mock.patch or other standard decorator-wrappings - are used with test methods. This fixues issue346 and should - help with random "xdist" collection failures. Thanks to - Ronny Pfannschmidt and Donald Stufft for helping to isolate it. - -- fix issue357 - special case "-k" expressions to allow for - filtering with simple strings that are not valid python expressions. - Examples: "-k 1.3" matches all tests parametrized with 1.3. - "-k None" filters all tests that have "None" in their name - and conversely "-k 'not None'". - Previously these examples would raise syntax errors. - -- fix issue384 by removing the trial support code - since the unittest compat enhancements allow - trial to handle it on its own - -- don't hide an ImportError when importing a plugin produces one. - fixes issue375. - -- fix issue275 - allow usefixtures and autouse fixtures - for running doctest text files. - -- fix issue380 by making --resultlog only rely on longrepr instead - of the "reprcrash" attribute which only exists sometimes. - -- address issue122: allow @pytest.fixture(params=iterator) by exploding - into a list early on. - -- fix pexpect-3.0 compatibility for pytest's own tests. - (fixes issue386) - -- allow nested parametrize-value markers, thanks James Lan for the PR. - -- fix unicode handling with new monkeypatch.setattr(import_path, value) - API. Thanks Rob Dennis. Fixes issue371. - -- fix unicode handling with junitxml, fixes issue368. - -- In assertion rewriting mode on Python 2, fix the detection of coding - cookies. See issue #330. - -- make "--runxfail" turn imperative pytest.xfail calls into no ops - (it already did neutralize pytest.mark.xfail markers) - -- refine pytest / pkg_resources interactions: The AssertionRewritingHook - PEP302 compliant loader now registers itself with setuptools/pkg_resources - properly so that the pkg_resources.resource_stream method works properly. - Fixes issue366. Thanks for the investigations and full PR to Jason R. Coombs. - -- pytestconfig fixture is now session-scoped as it is the same object during the - whole test run. Fixes issue370. - -- avoid one surprising case of marker malfunction/confusion:: - - @pytest.mark.some(lambda arg: ...) - def test_function(): - - would not work correctly because pytest assumes @pytest.mark.some - gets a function to be decorated already. We now at least detect if this - arg is a lambda and thus the example will work. Thanks Alex Gaynor - for bringing it up. - -- xfail a test on pypy that checks wrong encoding/ascii (pypy does - not error out). fixes issue385. - -- internally make varnames() deal with classes's __init__, - although it's not needed by pytest itself atm. Also - fix caching. Fixes issue376. - -- fix issue221 - handle importing of namespace-package with no - __init__.py properly. - -- refactor internal FixtureRequest handling to avoid monkeypatching. - One of the positive user-facing effects is that the "request" object - can now be used in closures. - -- fixed version comparison in pytest.importskip(modname, minverstring) - -- fix issue377 by clarifying in the nose-compat docs that pytest - does not duplicate the unittest-API into the "plain" namespace. - -- fix verbose reporting for @mock'd test functions - -2.4.2 (2013-10-04) -================== - -- on Windows require colorama and a newer py lib so that py.io.TerminalWriter() - now uses colorama instead of its own ctypes hacks. (fixes issue365) - thanks Paul Moore for bringing it up. - -- fix "-k" matching of tests where "repr" and "attr" and other names would - cause wrong matches because of an internal implementation quirk - (don't ask) which is now properly implemented. fixes issue345. - -- avoid tmpdir fixture to create too long filenames especially - when parametrization is used (issue354) - -- fix pytest-pep8 and pytest-flakes / pytest interactions - (collection names in mark plugin was assuming an item always - has a function which is not true for those plugins etc.) - Thanks Andi Zeidler. - -- introduce node.get_marker/node.add_marker API for plugins - like pytest-pep8 and pytest-flakes to avoid the messy - details of the node.keywords pseudo-dicts. Adapted - docs. - -- remove attempt to "dup" stdout at startup as it's icky. - the normal capturing should catch enough possibilities - of tests messing up standard FDs. - -- add pluginmanager.do_configure(config) as a link to - config.do_configure() for plugin-compatibility - -2.4.1 (2013-10-02) -================== - -- When using parser.addoption() unicode arguments to the - "type" keyword should also be converted to the respective types. - thanks Floris Bruynooghe, @dnozay. (fixes issue360 and issue362) - -- fix dotted filename completion when using argcomplete - thanks Anthon van der Neuth. (fixes issue361) - -- fix regression when a 1-tuple ("arg",) is used for specifying - parametrization (the values of the parametrization were passed - nested in a tuple). Thanks Donald Stufft. - -- merge doc typo fixes, thanks Andy Dirnberger - -2.4 -=== - -known incompatibilities: - -- if calling --genscript from python2.7 or above, you only get a - standalone script which works on python2.7 or above. Use Python2.6 - to also get a python2.5 compatible version. - -- all xunit-style teardown methods (nose-style, pytest-style, - unittest-style) will not be called if the corresponding setup method failed, - see issue322 below. - -- the pytest_plugin_unregister hook wasn't ever properly called - and there is no known implementation of the hook - so it got removed. - -- pytest.fixture-decorated functions cannot be generators (i.e. use - yield) anymore. This change might be reversed in 2.4.1 if it causes - unforeseen real-life issues. However, you can always write and return - an inner function/generator and change the fixture consumer to iterate - over the returned generator. This change was done in lieu of the new - ``pytest.yield_fixture`` decorator, see below. - -new features: - -- experimentally introduce a new ``pytest.yield_fixture`` decorator - which accepts exactly the same parameters as pytest.fixture but - mandates a ``yield`` statement instead of a ``return statement`` from - fixture functions. This allows direct integration with "with-style" - context managers in fixture functions and generally avoids registering - of finalization callbacks in favour of treating the "after-yield" as - teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris - Bruynooghe, Ronny Pfannschmidt and many others for discussions. - -- allow boolean expression directly with skipif/xfail - if a "reason" is also specified. Rework skipping documentation - to recommend "condition as booleans" because it prevents surprises - when importing markers between modules. Specifying conditions - as strings will remain fully supported. - -- reporting: color the last line red or green depending if - failures/errors occurred or everything passed. thanks Christian - Theunert. - -- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no - "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut. - -- fix issue181: --pdb now also works on collect errors (and - on internal errors) . This was implemented by a slight internal - refactoring and the introduction of a new hook - ``pytest_exception_interact`` hook (see next item). - -- fix issue341: introduce new experimental hook for IDEs/terminals to - intercept debugging: ``pytest_exception_interact(node, call, report)``. - -- new monkeypatch.setattr() variant to provide a shorter - invocation for patching out classes/functions from modules: - - monkeypatch.setattr("requests.get", myfunc) - - will replace the "get" function of the "requests" module with ``myfunc``. - -- fix issue322: tearDownClass is not run if setUpClass failed. Thanks - Mathieu Agopian for the initial fix. Also make all of pytest/nose - finalizer mimic the same generic behaviour: if a setupX exists and - fails, don't run teardownX. This internally introduces a new method - "node.addfinalizer()" helper which can only be called during the setup - phase of a node. - -- simplify pytest.mark.parametrize() signature: allow to pass a - CSV-separated string to specify argnames. For example: - ``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])`` - works as well as the previous: - ``pytest.mark.parametrize(("input", "expected"), ...)``. - -- add support for setUpModule/tearDownModule detection, thanks Brian Okken. - -- integrate tab-completion on options through use of "argcomplete". - Thanks Anthon van der Neut for the PR. - -- change option names to be hyphen-separated long options but keep the - old spelling backward compatible. py.test -h will only show the - hyphenated version, for example "--collect-only" but "--collectonly" - will remain valid as well (for backward-compat reasons). Many thanks to - Anthon van der Neut for the implementation and to Hynek Schlawack for - pushing us. - -- fix issue 308 - allow to mark/xfail/skip individual parameter sets - when parametrizing. Thanks Brianna Laugher. - -- call new experimental pytest_load_initial_conftests hook to allow - 3rd party plugins to do something before a conftest is loaded. - -Bug fixes: - -- fix issue358 - capturing options are now parsed more properly - by using a new parser.parse_known_args method. - -- pytest now uses argparse instead of optparse (thanks Anthon) which - means that "argparse" is added as a dependency if installing into python2.6 - environments or below. - -- fix issue333: fix a case of bad unittest/pytest hook interaction. - -- PR27: correctly handle nose.SkipTest during collection. Thanks - Antonio Cuni, Ronny Pfannschmidt. - -- fix issue355: junitxml puts name="pytest" attribute to testsuite tag. - -- fix issue336: autouse fixture in plugins should work again. - -- fix issue279: improve object comparisons on assertion failure - for standard datatypes and recognise collections.abc. Thanks to - Brianna Laugher and Mathieu Agopian. - -- fix issue317: assertion rewriter support for the is_package method - -- fix issue335: document py.code.ExceptionInfo() object returned - from pytest.raises(), thanks Mathieu Agopian. - -- remove implicit distribute_setup support from setup.py. - -- fix issue305: ignore any problems when writing pyc files. - -- SO-17664702: call fixture finalizers even if the fixture function - partially failed (finalizers would not always be called before) - -- fix issue320 - fix class scope for fixtures when mixed with - module-level functions. Thanks Anatloy Bubenkoff. - -- you can specify "-q" or "-qq" to get different levels of "quieter" - reporting (thanks Katarzyna Jachim) - -- fix issue300 - Fix order of conftest loading when starting py.test - in a subdirectory. - -- fix issue323 - sorting of many module-scoped arg parametrizations - -- make sessionfinish hooks execute with the same cwd-context as at - session start (helps fix plugin behaviour which write output files - with relative path such as pytest-cov) - -- fix issue316 - properly reference collection hooks in docs - -- fix issue 306 - cleanup of -k/-m options to only match markers/test - names/keywords respectively. Thanks Wouter van Ackooy. - -- improved doctest counting for doctests in python modules -- - files without any doctest items will not show up anymore - and doctest examples are counted as separate test items. - thanks Danilo Bellini. - -- fix issue245 by depending on the released py-1.4.14 - which fixes py.io.dupfile to work with files with no - mode. Thanks Jason R. Coombs. - -- fix junitxml generation when test output contains control characters, - addressing issue267, thanks Jaap Broekhuizen - -- fix issue338: honor --tb style for setup/teardown errors as well. Thanks Maho. - -- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin. - -- better parametrize error messages, thanks Brianna Laugher - -- pytest_terminal_summary(terminalreporter) hooks can now use - ".section(title)" and ".line(msg)" methods to print extra - information at the end of a test run. - -2.3.5 (2013-04-30) -================== - -- fix issue169: respect --tb=style with setup/teardown errors as well. - -- never consider a fixture function for test function collection - -- allow re-running of test items / helps to fix pytest-reruntests plugin - and also help to keep less fixture/resource references alive - -- put captured stdout/stderr into junitxml output even for passing tests - (thanks Adam Goucher) - -- Issue 265 - integrate nose setup/teardown with setupstate - so it doesn't try to teardown if it did not setup - -- issue 271 - don't write junitxml on slave nodes - -- Issue 274 - don't try to show full doctest example - when doctest does not know the example location - -- issue 280 - disable assertion rewriting on buggy CPython 2.6.0 - -- inject "getfixture()" helper to retrieve fixtures from doctests, - thanks Andreas Zeidler - -- issue 259 - when assertion rewriting, be consistent with the default - source encoding of ASCII on Python 2 - -- issue 251 - report a skip instead of ignoring classes with init - -- issue250 unicode/str mixes in parametrization names and values now works - -- issue257, assertion-triggered compilation of source ending in a - comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6) - -- fix --genscript option to generate standalone scripts that also - work with python3.3 (importer ordering) - -- issue171 - in assertion rewriting, show the repr of some - global variables - -- fix option help for "-k" - -- move long description of distribution into README.rst - -- improve docstring for metafunc.parametrize() - -- fix bug where using capsys with pytest.set_trace() in a test - function would break when looking at capsys.readouterr() - -- allow to specify prefixes starting with "_" when - customizing python_functions test discovery. (thanks Graham Horler) - -- improve PYTEST_DEBUG tracing output by putting - extra data on a new lines with additional indent - -- ensure OutcomeExceptions like skip/fail have initialized exception attributes - -- issue 260 - don't use nose special setup on plain unittest cases - -- fix issue134 - print the collect errors that prevent running specified test items - -- fix issue266 - accept unicode in MarkEvaluator expressions - -2.3.4 (2012-11-20) -================== - -- yielded test functions will now have autouse-fixtures active but - cannot accept fixtures as funcargs - it's anyway recommended to - rather use the post-2.0 parametrize features instead of yield, see: - http://pytest.org/latest/example/parametrize.html -- fix autouse-issue where autouse-fixtures would not be discovered - if defined in an a/conftest.py file and tests in a/tests/test_some.py -- fix issue226 - LIFO ordering for fixture teardowns -- fix issue224 - invocations with >256 char arguments now work -- fix issue91 - add/discuss package/directory level setups in example -- allow to dynamically define markers via - item.keywords[...]=assignment integrating with "-m" option -- make "-k" accept an expressions the same as with "-m" so that one - can write: -k "name1 or name2" etc. This is a slight incompatibility - if you used special syntax like "TestClass.test_method" which you now - need to write as -k "TestClass and test_method" to match a certain - method in a certain test class. - -2.3.3 (2012-11-06) -================== - -- fix issue214 - parse modules that contain special objects like e. g. - flask's request object which blows up on getattr access if no request - is active. thanks Thomas Waldmann. - -- fix issue213 - allow to parametrize with values like numpy arrays that - do not support an __eq__ operator - -- fix issue215 - split test_python.org into multiple files - -- fix issue148 - @unittest.skip on classes is now recognized and avoids - calling setUpClass/tearDownClass, thanks Pavel Repin - -- fix issue209 - reintroduce python2.4 support by depending on newer - pylib which re-introduced statement-finding for pre-AST interpreters - -- nose support: only call setup if it's a callable, thanks Andrew - Taumoefolau - -- fix issue219 - add py2.4-3.3 classifiers to TROVE list - -- in tracebacks *,** arg values are now shown next to normal arguments - (thanks Manuel Jacob) - -- fix issue217 - support mock.patch with pytest's fixtures - note that - you need either mock-1.0.1 or the python3.3 builtin unittest.mock. - -- fix issue127 - improve documentation for pytest_addoption() and - add a ``config.getoption(name)`` helper function for consistency. - -2.3.2 (2012-10-25) -================== - -- fix issue208 and fix issue29 use new py version to avoid long pauses - when printing tracebacks in long modules - -- fix issue205 - conftests in subdirs customizing - pytest_pycollect_makemodule and pytest_pycollect_makeitem - now work properly - -- fix teardown-ordering for parametrized setups - -- fix issue127 - better documentation for pytest_addoption - and related objects. - -- fix unittest behaviour: TestCase.runtest only called if there are - test methods defined - -- improve trial support: don't collect its empty - unittest.TestCase.runTest() method - -- "python setup.py test" now works with pytest itself - -- fix/improve internal/packaging related bits: - - - exception message check of test_nose.py now passes on python33 as well - - - issue206 - fix test_assertrewrite.py to work when a global - PYTHONDONTWRITEBYTECODE=1 is present - - - add tox.ini to pytest distribution so that ignore-dirs and others config - bits are properly distributed for maintainers who run pytest-own tests - -2.3.1 (2012-10-20) -================== - -- fix issue202 - fix regression: using "self" from fixture functions now - works as expected (it's the same "self" instance that a test method - which uses the fixture sees) - -- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems - due to pexpect not supporting it properly (hanging) - -- link to web pages from --markers output which provides help for - pytest.mark.* usage. - -2.3.0 (2012-10-19) -================== - -- fix issue202 - better automatic names for parametrized test functions -- fix issue139 - introduce @pytest.fixture which allows direct scoping - and parametrization of funcarg factories. -- fix issue198 - conftest fixtures were not found on windows32 in some - circumstances with nested directory structures due to path manipulation issues -- fix issue193 skip test functions with were parametrized with empty - parameter sets -- fix python3.3 compat, mostly reporting bits that previously depended - on dict ordering -- introduce re-ordering of tests by resource and parametrization setup - which takes precedence to the usual file-ordering -- fix issue185 monkeypatching time.time does not cause pytest to fail -- fix issue172 duplicate call of pytest.fixture decoratored setup_module - functions -- fix junitxml=path construction so that if tests change the - current working directory and the path is a relative path - it is constructed correctly from the original current working dir. -- fix "python setup.py test" example to cause a proper "errno" return -- fix issue165 - fix broken doc links and mention stackoverflow for FAQ -- catch unicode-issues when writing failure representations - to terminal to prevent the whole session from crashing -- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip - will now take precedence before xfail-markers because we - can't determine xfail/xpass status in case of a skip. see also: - http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get - -- always report installed 3rd party plugins in the header of a test run - -- fix issue160: a failing setup of an xfail-marked tests should - be reported as xfail (not xpass) - -- fix issue128: show captured output when capsys/capfd are used - -- fix issue179: properly show the dependency chain of factories - -- pluginmanager.register(...) now raises ValueError if the - plugin has been already registered or the name is taken - -- fix issue159: improve http://pytest.org/latest/faq.html - especially with respect to the "magic" history, also mention - pytest-django, trial and unittest integration. - -- make request.keywords and node.keywords writable. All descendant - collection nodes will see keyword values. Keywords are dictionaries - containing markers and other info. - -- fix issue 178: xml binary escapes are now wrapped in py.xml.raw - -- fix issue 176: correctly catch the builtin AssertionError - even when we replaced AssertionError with a subclass on the - python level - -- factory discovery no longer fails with magic global callables - that provide no sane __code__ object (mock.call for example) - -- fix issue 182: testdir.inprocess_run now considers passed plugins - -- fix issue 188: ensure sys.exc_info is clear on python2 - before calling into a test - -- fix issue 191: add unittest TestCase runTest method support -- fix issue 156: monkeypatch correctly handles class level descriptors - -- reporting refinements: - - - pytest_report_header now receives a "startdir" so that - you can use startdir.bestrelpath(yourpath) to show - nice relative path - - - allow plugins to implement both pytest_report_header and - pytest_sessionstart (sessionstart is invoked first). - - - don't show deselected reason line if there is none - - - py.test -vv will show all of assert comparisons instead of truncating - -2.2.4 (2012-05-22) -================== - -- fix error message for rewritten assertions involving the % operator -- fix issue 126: correctly match all invalid xml characters for junitxml - binary escape -- fix issue with unittest: now @unittest.expectedFailure markers should - be processed correctly (you can also use @pytest.mark markers) -- document integration with the extended distribute/setuptools test commands -- fix issue 140: properly get the real functions - of bound classmethods for setup/teardown_class -- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net -- fix issue #143: call unconfigure/sessionfinish always when - configure/sessionstart where called -- fix issue #144: better mangle test ids to junitxml classnames -- upgrade distribute_setup.py to 0.6.27 - -2.2.3 (2012-02-05) -================== - -- fix uploaded package to only include necessary files - -2.2.2 (2012-02-05) -================== - -- fix issue101: wrong args to unittest.TestCase test function now - produce better output -- fix issue102: report more useful errors and hints for when a - test directory was renamed and some pyc/__pycache__ remain -- fix issue106: allow parametrize to be applied multiple times - e.g. from module, class and at function level. -- fix issue107: actually perform session scope finalization -- don't check in parametrize if indirect parameters are funcarg names -- add chdir method to monkeypatch funcarg -- fix crash resulting from calling monkeypatch undo a second time -- fix issue115: make --collectonly robust against early failure - (missing files/directories) -- "-qq --collectonly" now shows only files and the number of tests in them -- "-q --collectonly" now shows test ids -- allow adding of attributes to test reports such that it also works - with distributed testing (no upgrade of pytest-xdist needed) - -2.2.1 (2011-12-16) -================== - -- fix issue99 (in pytest and py) internallerrors with resultlog now - produce better output - fixed by normalizing pytest_internalerror - input arguments. -- fix issue97 / traceback issues (in pytest and py) improve traceback output - in conjunction with jinja2 and cython which hack tracebacks -- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns": - the final test in a test node will now run its teardown directly - instead of waiting for the end of the session. Thanks Dave Hunt for - the good reporting and feedback. The pytest_runtest_protocol as well - as the pytest_runtest_teardown hooks now have "nextitem" available - which will be None indicating the end of the test run. -- fix collection crash due to unknown-source collected items, thanks - to Ralf Schmitt (fixed by depending on a more recent pylib) - -2.2.0 (2011-11-18) -================== - -- fix issue90: introduce eager tearing down of test items so that - teardown function are called earlier. -- add an all-powerful metafunc.parametrize function which allows to - parametrize test function arguments in multiple steps and therefore - from independent plugins and places. -- add a @pytest.mark.parametrize helper which allows to easily - call a test function with different argument values -- Add examples to the "parametrize" example page, including a quick port - of Test scenarios and the new parametrize function and decorator. -- introduce registration for "pytest.mark.*" helpers via ini-files - or through plugin hooks. Also introduce a "--strict" option which - will treat unregistered markers as errors - allowing to avoid typos and maintain a well described set of markers - for your test suite. See exaples at http://pytest.org/latest/mark.html - and its links. -- issue50: introduce "-m marker" option to select tests based on markers - (this is a stricter and more predictable version of '-k' in that "-m" - only matches complete markers and has more obvious rules for and/or - semantics. -- new feature to help optimizing the speed of your tests: - --durations=N option for displaying N slowest test calls - and setup/teardown methods. -- fix issue87: --pastebin now works with python3 -- fix issue89: --pdb with unexpected exceptions in doctest work more sensibly -- fix and cleanup pytest's own test suite to not leak FDs -- fix issue83: link to generated funcarg list -- fix issue74: pyarg module names are now checked against imp.find_module false positives -- fix compatibility with twisted/trial-11.1.0 use cases -- simplify Node.listchain -- simplify junitxml output code by relying on py.xml -- add support for skip properties on unittest classes and functions - -2.1.3 (2011-10-18) -================== - -- fix issue79: assertion rewriting failed on some comparisons in boolops -- correctly handle zero length arguments (a la pytest '') -- fix issue67 / junitxml now contains correct test durations, thanks ronny -- fix issue75 / skipping test failure on jython -- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests - -2.1.2 (2011-09-24) -================== - -- fix assertion rewriting on files with windows newlines on some Python versions -- refine test discovery by package/module name (--pyargs), thanks Florian Mayer -- fix issue69 / assertion rewriting fixed on some boolean operations -- fix issue68 / packages now work with assertion rewriting -- fix issue66: use different assertion rewriting caches when the -O option is passed -- don't try assertion rewriting on Jython, use reinterp - -2.1.1 -===== - -- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks -- fix issue60 / fix error conditions involving the creation of __pycache__ -- fix issue63 / assertion rewriting on inserts involving strings containing '%' -- fix assertion rewriting on calls with a ** arg -- don't cache rewritten modules if bytecode generation is disabled -- fix assertion rewriting in read-only directories -- fix issue59: provide system-out/err tags for junitxml output -- fix issue61: assertion rewriting on boolean operations with 3 or more operands -- you can now build a man page with "cd doc ; make man" - -2.1.0 (2011-07-09) -================== - -- fix issue53 call nosestyle setup functions with correct ordering -- fix issue58 and issue59: new assertion code fixes -- merge Benjamin's assertionrewrite branch: now assertions - for test modules on python 2.6 and above are done by rewriting - the AST and saving the pyc file before the test module is imported. - see doc/assert.txt for more info. -- fix issue43: improve doctests with better traceback reporting on - unexpected exceptions -- fix issue47: timing output in junitxml for test cases is now correct -- fix issue48: typo in MarkInfo repr leading to exception -- fix issue49: avoid confusing error when initizaliation partially fails -- fix issue44: env/username expansion for junitxml file path -- show releaselevel information in test runs for pypy -- reworked doc pages for better navigation and PDF generation -- report KeyboardInterrupt even if interrupted during session startup -- fix issue 35 - provide PDF doc version and download link from index page - -2.0.3 (2011-05-11) -================== - -- fix issue38: nicer tracebacks on calls to hooks, particularly early - configure/sessionstart ones - -- fix missing skip reason/meta information in junitxml files, reported - via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html - -- fix issue34: avoid collection failure with "test" prefixed classes - deriving from object. - -- don't require zlib (and other libs) for genscript plugin without - --genscript actually being used. - -- speed up skips (by not doing a full traceback representation - internally) - -- fix issue37: avoid invalid characters in junitxml's output - -2.0.2 (2011-03-09) -================== - -- tackle issue32 - speed up test runs of very quick test functions - by reducing the relative overhead - -- fix issue30 - extended xfail/skipif handling and improved reporting. - If you have a syntax error in your skip/xfail - expressions you now get nice error reports. - - Also you can now access module globals from xfail/skipif - expressions so that this for example works now:: - - import pytest - import mymodule - @pytest.mark.skipif("mymodule.__version__[0] == "1") - def test_function(): - pass - - This will not run the test function if the module's version string - does not start with a "1". Note that specifying a string instead - of a boolean expressions allows py.test to report meaningful information - when summarizing a test run as to what conditions lead to skipping - (or xfail-ing) tests. - -- fix issue28 - setup_method and pytest_generate_tests work together - The setup_method fixture method now gets called also for - test function invocations generated from the pytest_generate_tests - hook. - -- fix issue27 - collectonly and keyword-selection (-k) now work together - Also, if you do "py.test --collectonly -q" you now get a flat list - of test ids that you can use to paste to the py.test commandline - in order to execute a particular test. - -- fix issue25 avoid reported problems with --pdb and python3.2/encodings output - -- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP - Starting with Python3.2 os.symlink may be supported. By requiring - a newer py lib version the py.path.local() implementation acknowledges - this. - -- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular - thanks to Laura Creighton who also reviewed parts of the documentation. - -- fix slightly wrong output of verbose progress reporting for classes - (thanks Amaury) - -- more precise (avoiding of) deprecation warnings for node.Class|Function accesses - -- avoid std unittest assertion helper code in tracebacks (thanks Ronny) - -2.0.1 (2011-02-07) -================== - -- refine and unify initial capturing so that it works nicely - even if the logging module is used on an early-loaded conftest.py - file or plugin. -- allow to omit "()" in test ids to allow for uniform test ids - as produced by Alfredo's nice pytest.vim plugin. -- fix issue12 - show plugin versions with "--version" and - "--traceconfig" and also document how to add extra information - to reporting test header -- fix issue17 (import-* reporting issue on python3) by - requiring py>1.4.0 (1.4.1 is going to include it) -- fix issue10 (numpy arrays truth checking) by refining - assertion interpretation in py lib -- fix issue15: make nose compatibility tests compatible - with python3 (now that nose-1.0 supports python3) -- remove somewhat surprising "same-conftest" detection because - it ignores conftest.py when they appear in several subdirs. -- improve assertions ("not in"), thanks Floris Bruynooghe -- improve behaviour/warnings when running on top of "python -OO" - (assertions and docstrings are turned off, leading to potential - false positives) -- introduce a pytest_cmdline_processargs(args) hook - to allow dynamic computation of command line arguments. - This fixes a regression because py.test prior to 2.0 - allowed to set command line options from conftest.py - files which so far pytest-2.0 only allowed from ini-files now. -- fix issue7: assert failures in doctest modules. - unexpected failures in doctests will not generally - show nicer, i.e. within the doctest failing context. -- fix issue9: setup/teardown functions for an xfail-marked - test will report as xfail if they fail but report as normally - passing (not xpassing) if they succeed. This only is true - for "direct" setup/teardown invocations because teardown_class/ - teardown_module cannot closely relate to a single test. -- fix issue14: no logging errors at process exit -- refinements to "collecting" output on non-ttys -- refine internal plugin registration and --traceconfig output -- introduce a mechanism to prevent/unregister plugins from the - command line, see http://pytest.org/plugins.html#cmdunregister -- activate resultlog plugin by default -- fix regression wrt yielded tests which due to the - collection-before-running semantics were not - setup as with pytest 1.3.4. Note, however, that - the recommended and much cleaner way to do test - parametraization remains the "pytest_generate_tests" - mechanism, see the docs. - -2.0.0 (2010-11-25) -================== - -- pytest-2.0 is now its own package and depends on pylib-2.0 -- new ability: python -m pytest / python -m pytest.main ability -- new python invocation: pytest.main(args, plugins) to load - some custom plugins early. -- try harder to run unittest test suites in a more compatible manner - by deferring setup/teardown semantics to the unittest package. - also work harder to run twisted/trial and Django tests which - should now basically work by default. -- introduce a new way to set config options via ini-style files, - by default setup.cfg and tox.ini files are searched. The old - ways (certain environment variables, dynamic conftest.py reading - is removed). -- add a new "-q" option which decreases verbosity and prints a more - nose/unittest-style "dot" output. -- fix issue135 - marks now work with unittest test cases as well -- fix issue126 - introduce py.test.set_trace() to trace execution via - PDB during the running of tests even if capturing is ongoing. -- fix issue123 - new "python -m py.test" invocation for py.test - (requires Python 2.5 or above) -- fix issue124 - make reporting more resilient against tests opening - files on filedescriptor 1 (stdout). -- fix issue109 - sibling conftest.py files will not be loaded. - (and Directory collectors cannot be customized anymore from a Directory's - conftest.py - this needs to happen at least one level up). -- introduce (customizable) assertion failure representations and enhance - output on assertion failures for comparisons and other cases (Floris Bruynooghe) -- nose-plugin: pass through type-signature failures in setup/teardown - functions instead of not calling them (Ed Singleton) -- remove py.test.collect.Directory (follows from a major refactoring - and simplification of the collection process) -- majorly reduce py.test core code, shift function/python testing to own plugin -- fix issue88 (finding custom test nodes from command line arg) -- refine 'tmpdir' creation, will now create basenames better associated - with test names (thanks Ronny) -- "xpass" (unexpected pass) tests don't cause exitcode!=0 -- fix issue131 / issue60 - importing doctests in __init__ files used as namespace packages -- fix issue93 stdout/stderr is captured while importing conftest.py -- fix bug: unittest collected functions now also can have "pytestmark" - applied at class/module level -- add ability to use "class" level for cached_setup helper -- fix strangeness: mark.* objects are now immutable, create new instances - -1.3.4 (2010-09-14) -================== - -- fix issue111: improve install documentation for windows -- fix issue119: fix custom collectability of __init__.py as a module -- fix issue116: --doctestmodules work with __init__.py files as well -- fix issue115: unify internal exception passthrough/catching/GeneratorExit -- fix issue118: new --tb=native for presenting cpython-standard exceptions - -1.3.3 (2010-07-30) -================== - -- fix issue113: assertion representation problem with triple-quoted strings - (and possibly other cases) -- make conftest loading detect that a conftest file with the same - content was already loaded, avoids surprises in nested directory structures - which can be produced e.g. by Hudson. It probably removes the need to use - --confcutdir in most cases. -- fix terminal coloring for win32 - (thanks Michael Foord for reporting) -- fix weirdness: make terminal width detection work on stdout instead of stdin - (thanks Armin Ronacher for reporting) -- remove trailing whitespace in all py/text distribution files - -1.3.2 (2010-07-08) -================== - -**New features** - -- fix issue103: introduce py.test.raises as context manager, examples:: - - with py.test.raises(ZeroDivisionError): - x = 0 - 1 / x - - with py.test.raises(RuntimeError) as excinfo: - call_something() - - # you may do extra checks on excinfo.value|type|traceback here - - (thanks Ronny Pfannschmidt) - -- Funcarg factories can now dynamically apply a marker to a - test invocation. This is for example useful if a factory - provides parameters to a test which are expected-to-fail:: - - def pytest_funcarg__arg(request): - request.applymarker(py.test.mark.xfail(reason="flaky config")) - ... - - def test_function(arg): - ... - -- improved error reporting on collection and import errors. This makes - use of a more general mechanism, namely that for custom test item/collect - nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can - override it to return a string error representation of your choice - which is going to be reported as a (red) string. - -- introduce '--junitprefix=STR' option to prepend a prefix - to all reports in the junitxml file. - -**Bug fixes** - -- make tests and the ``pytest_recwarn`` plugin in particular fully compatible - to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that - you can properly check for their existence in a cross-python manner). -- refine --pdb: ignore xfailed tests, unify its TB-reporting and - don't display failures again at the end. -- fix assertion interpretation with the ** operator (thanks Benjamin Peterson) -- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson) -- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous) -- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny) -- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson) -- fix py.code.compile(source) to generate unique filenames -- fix assertion re-interp problems on PyPy, by defering code - compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot) -- fix py.path.local.pyimport() to work with directories -- streamline py.path.local.mkdtemp implementation and usage -- don't print empty lines when showing junitxml-filename -- add optional boolean ignore_errors parameter to py.path.local.remove -- fix terminal writing on win32/python2.4 -- py.process.cmdexec() now tries harder to return properly encoded unicode objects - on all python versions -- install plain py.test/py.which scripts also for Jython, this helps to - get canonical script paths in virtualenv situations -- make path.bestrelpath(path) return ".", note that when calling - X.bestrelpath the assumption is that X is a directory. -- make initial conftest discovery ignore "--" prefixed arguments -- fix resultlog plugin when used in a multicpu/multihost xdist situation - (thanks Jakub Gustak) -- perform distributed testing related reporting in the xdist-plugin - rather than having dist-related code in the generic py.test - distribution -- fix homedir detection on Windows -- ship distribute_setup.py version 0.6.13 - -1.3.1 (2010-05-25) -================== - -**New features** - -- issue91: introduce new py.test.xfail(reason) helper - to imperatively mark a test as expected to fail. Can - be used from within setup and test functions. This is - useful especially for parametrized tests when certain - configurations are expected-to-fail. In this case the - declarative approach with the @py.test.mark.xfail cannot - be used as it would mark all configurations as xfail. - -- issue102: introduce new --maxfail=NUM option to stop - test runs after NUM failures. This is a generalization - of the '-x' or '--exitfirst' option which is now equivalent - to '--maxfail=1'. Both '-x' and '--maxfail' will - now also print a line near the end indicating the Interruption. - -- issue89: allow py.test.mark decorators to be used on classes - (class decorators were introduced with python2.6) and - also allow to have multiple markers applied at class/module level - by specifying a list. - -- improve and refine letter reporting in the progress bar: - . pass - f failed test - s skipped tests (reminder: use for dependency/platform mismatch only) - x xfailed test (test that was expected to fail) - X xpassed test (test that was expected to fail but passed) - - You can use any combination of 'fsxX' with the '-r' extended - reporting option. The xfail/xpass results will show up as - skipped tests in the junitxml output - which also fixes - issue99. - -- make py.test.cmdline.main() return the exitstatus instead of raising - SystemExit and also allow it to be called multiple times. This of - course requires that your application and tests are properly teared - down and don't have global state. - -**Bug Fixes** - -- improved traceback presentation: - - improved and unified reporting for "--tb=short" option - - Errors during test module imports are much shorter, (using --tb=short style) - - raises shows shorter more relevant tracebacks - - --fulltrace now more systematically makes traces longer / inhibits cutting - -- improve support for raises and other dynamically compiled code by - manipulating python's linecache.cache instead of the previous - rather hacky way of creating custom code objects. This makes - it seemlessly work on Jython and PyPy where it previously didn't. - -- fix issue96: make capturing more resilient against Control-C - interruptions (involved somewhat substantial refactoring - to the underlying capturing functionality to avoid race - conditions). - -- fix chaining of conditional skipif/xfail decorators - so it works now - as expected to use multiple @py.test.mark.skipif(condition) decorators, - including specific reporting which of the conditions lead to skipping. - -- fix issue95: late-import zlib so that it's not required - for general py.test startup. - -- fix issue94: make reporting more robust against bogus source code - (and internally be more careful when presenting unexpected byte sequences) - - -1.3.0 (2010-05-05) -================== - -- deprecate --report option in favour of a new shorter and easier to - remember -r option: it takes a string argument consisting of any - combination of 'xfsX' characters. They relate to the single chars - you see during the dotted progress printing and will print an extra line - per test at the end of the test run. This extra line indicates the exact - position or test ID that you directly paste to the py.test cmdline in order - to re-run a particular test. - -- allow external plugins to register new hooks via the new - pytest_addhooks(pluginmanager) hook. The new release of - the pytest-xdist plugin for distributed and looponfailing - testing requires this feature. - -- add a new pytest_ignore_collect(path, config) hook to allow projects and - plugins to define exclusion behaviour for their directory structure - - for example you may define in a conftest.py this method:: - - def pytest_ignore_collect(path): - return path.check(link=1) - - to prevent even a collection try of any tests in symlinked dirs. - -- new pytest_pycollect_makemodule(path, parent) hook for - allowing customization of the Module collection object for a - matching test module. - -- extend and refine xfail mechanism: - ``@py.test.mark.xfail(run=False)`` do not run the decorated test - ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries - specifying ``--runxfail`` on command line virtually ignores xfail markers - -- expose (previously internal) commonly useful methods: - py.io.get_terminal_with() -> return terminal width - py.io.ansi_print(...) -> print colored/bold text on linux/win32 - py.io.saferepr(obj) -> return limited representation string - -- expose test outcome related exceptions as py.test.skip.Exception, - py.test.raises.Exception etc., useful mostly for plugins - doing special outcome interpretation/tweaking - -- (issue85) fix junitxml plugin to handle tests with non-ascii output - -- fix/refine python3 compatibility (thanks Benjamin Peterson) - -- fixes for making the jython/win32 combination work, note however: - jython2.5.1/win32 does not provide a command line launcher, see - http://bugs.jython.org/issue1491 . See pylib install documentation - for how to work around. - -- fixes for handling of unicode exception values and unprintable objects - -- (issue87) fix unboundlocal error in assertionold code - -- (issue86) improve documentation for looponfailing - -- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method - -- ship distribute_setup.py version 0.6.10 - -- added links to the new capturelog and coverage plugins - - -1.2.0 (2010-01-18) -================== - -- refined usage and options for "py.cleanup":: - - py.cleanup # remove "*.pyc" and "*$py.class" (jython) files - py.cleanup -e .swp -e .cache # also remove files with these extensions - py.cleanup -s # remove "build" and "dist" directory next to setup.py files - py.cleanup -d # also remove empty directories - py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'" - py.cleanup -n # dry run, only show what would be removed - -- add a new option "py.test --funcargs" which shows available funcargs - and their help strings (docstrings on their respective factory function) - for a given test path - -- display a short and concise traceback if a funcarg lookup fails - -- early-load "conftest.py" files in non-dot first-level sub directories. - allows to conveniently keep and access test-related options in a ``test`` - subdir and still add command line options. - -- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value - -- fix issue78: always call python-level teardown functions even if the - according setup failed. This includes refinements for calling setup_module/class functions - which will now only be called once instead of the previous behaviour where they'd be called - multiple times if they raise an exception (including a Skipped exception). Any exception - will be re-corded and associated with all tests in the according module/class scope. - -- fix issue63: assume <40 columns to be a bogus terminal width, default to 80 - -- fix pdb debugging to be in the correct frame on raises-related errors - -- update apipkg.py to fix an issue where recursive imports might - unnecessarily break importing - -- fix plugin links - -1.1.1 (2009-11-24) -================== - -- moved dist/looponfailing from py.test core into a new - separately released pytest-xdist plugin. - -- new junitxml plugin: --junitxml=path will generate a junit style xml file - which is processable e.g. by the Hudson CI system. - -- new option: --genscript=path will generate a standalone py.test script - which will not need any libraries installed. thanks to Ralf Schmitt. - -- new option: --ignore will prevent specified path from collection. - Can be specified multiple times. - -- new option: --confcutdir=dir will make py.test only consider conftest - files that are relative to the specified dir. - -- new funcarg: "pytestconfig" is the pytest config object for access - to command line args and can now be easily used in a test. - -- install ``py.test`` and ``py.which`` with a ``-$VERSION`` suffix to - disambiguate between Python3, python2.X, Jython and PyPy installed versions. - -- new "pytestconfig" funcarg allows access to test config object - -- new "pytest_report_header" hook can return additional lines - to be displayed at the header of a test run. - -- (experimental) allow "py.test path::name1::name2::..." for pointing - to a test within a test collection directly. This might eventually - evolve as a full substitute to "-k" specifications. - -- streamlined plugin loading: order is now as documented in - customize.html: setuptools, ENV, commandline, conftest. - also setuptools entry point names are turned to canonical namees ("pytest_*") - -- automatically skip tests that need 'capfd' but have no os.dup - -- allow pytest_generate_tests to be defined in classes as well - -- deprecate usage of 'disabled' attribute in favour of pytestmark -- deprecate definition of Directory, Module, Class and Function nodes - in conftest.py files. Use pytest collect hooks instead. - -- collection/item node specific runtest/collect hooks are only called exactly - on matching conftest.py files, i.e. ones which are exactly below - the filesystem path of an item - -- change: the first pytest_collect_directory hook to return something - will now prevent further hooks to be called. - -- change: figleaf plugin now requires --figleaf to run. Also - change its long command line options to be a bit shorter (see py.test -h). - -- change: pytest doctest plugin is now enabled by default and has a - new option --doctest-glob to set a pattern for file matches. - -- change: remove internal py._* helper vars, only keep py._pydir - -- robustify capturing to survive if custom pytest_runtest_setup - code failed and prevented the capturing setup code from running. - -- make py.test.* helpers provided by default plugins visible early - - works transparently both for pydoc and for interactive sessions - which will regularly see e.g. py.test.mark and py.test.importorskip. - -- simplify internal plugin manager machinery -- simplify internal collection tree by introducing a RootCollector node - -- fix assert reinterpreation that sees a call containing "keyword=..." - -- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish - hooks on slaves during dist-testing, report module/session teardown - hooks correctly. - -- fix issue65: properly handle dist-testing if no - execnet/py lib installed remotely. - -- skip some install-tests if no execnet is available - -- fix docs, fix internal bin/ script generation - - -1.1.0 (2009-11-05) -================== - -- introduce automatic plugin registration via 'pytest11' - entrypoints via setuptools' pkg_resources.iter_entry_points - -- fix py.test dist-testing to work with execnet >= 1.0.0b4 - -- re-introduce py.test.cmdline.main() for better backward compatibility - -- svn paths: fix a bug with path.check(versioned=True) for svn paths, - allow '%' in svn paths, make svnwc.update() default to interactive mode - like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction. - -- refine distributed tarball to contain test and no pyc files - -- try harder to have deprecation warnings for py.compat.* accesses - report a correct location - -1.0.3 -===== - -* adjust and improve docs - -* remove py.rest tool and internal namespace - it was - never really advertised and can still be used with - the old release if needed. If there is interest - it could be revived into its own tool i guess. - -* fix issue48 and issue59: raise an Error if the module - from an imported test file does not seem to come from - the filepath - avoids "same-name" confusion that has - been reported repeatedly - -* merged Ronny's nose-compatibility hacks: now - nose-style setup_module() and setup() functions are - supported - -* introduce generalized py.test.mark function marking - -* reshuffle / refine command line grouping - -* deprecate parser.addgroup in favour of getgroup which creates option group - -* add --report command line option that allows to control showing of skipped/xfailed sections - -* generalized skipping: a new way to mark python functions with skipif or xfail - at function, class and modules level based on platform or sys-module attributes. - -* extend py.test.mark decorator to allow for positional args - -* introduce and test "py.cleanup -d" to remove empty directories - -* fix issue #59 - robustify unittest test collection - -* make bpython/help interaction work by adding an __all__ attribute - to ApiModule, cleanup initpkg - -* use MIT license for pylib, add some contributors - -* remove py.execnet code and substitute all usages with 'execnet' proper - -* fix issue50 - cached_setup now caches more to expectations - for test functions with multiple arguments. - -* merge Jarko's fixes, issue #45 and #46 - -* add the ability to specify a path for py.lookup to search in - -* fix a funcarg cached_setup bug probably only occurring - in distributed testing and "module" scope with teardown. - -* many fixes and changes for making the code base python3 compatible, - many thanks to Benjamin Peterson for helping with this. - -* consolidate builtins implementation to be compatible with >=2.3, - add helpers to ease keeping 2 and 3k compatible code - -* deprecate py.compat.doctest|subprocess|textwrap|optparse - -* deprecate py.magic.autopath, remove py/magic directory - -* move pytest assertion handling to py/code and a pytest_assertion - plugin, add "--no-assert" option, deprecate py.magic namespaces - in favour of (less) py.code ones. - -* consolidate and cleanup py/code classes and files - -* cleanup py/misc, move tests to bin-for-dist - -* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg - -* consolidate py.log implementation, remove old approach. - -* introduce py.io.TextIO and py.io.BytesIO for distinguishing between - text/unicode and byte-streams (uses underlying standard lib io.* - if available) - -* make py.unittest_convert helper script available which converts "unittest.py" - style files into the simpler assert/direct-test-classes py.test/nosetests - style. The script was written by Laura Creighton. - -* simplified internal localpath implementation - -1.0.2 (2009-08-27) -================== - -* fixing packaging issues, triggered by fedora redhat packaging, - also added doc, examples and contrib dirs to the tarball. - -* added a documentation link to the new django plugin. - -1.0.1 (2009-08-19) -================== - -* added a 'pytest_nose' plugin which handles nose.SkipTest, - nose-style function/method/generator setup/teardown and - tries to report functions correctly. - -* capturing of unicode writes or encoded strings to sys.stdout/err - work better, also terminalwriting was adapted and somewhat - unified between windows and linux. - -* improved documentation layout and content a lot - -* added a "--help-config" option to show conftest.py / ENV-var names for - all longopt cmdline options, and some special conftest.py variables. - renamed 'conf_capture' conftest setting to 'option_capture' accordingly. - -* fix issue #27: better reporting on non-collectable items given on commandline - (e.g. pyc files) - -* fix issue #33: added --version flag (thanks Benjamin Peterson) - -* fix issue #32: adding support for "incomplete" paths to wcpath.status() - -* "Test" prefixed classes are *not* collected by default anymore if they - have an __init__ method - -* monkeypatch setenv() now accepts a "prepend" parameter - -* improved reporting of collection error tracebacks - -* simplified multicall mechanism and plugin architecture, - renamed some internal methods and argnames - -1.0.0 (2009-08-04) -================== - -* more terse reporting try to show filesystem path relatively to current dir -* improve xfail output a bit - -1.0.0b9 (2009-07-31) -==================== - -* cleanly handle and report final teardown of test setup - -* fix svn-1.6 compat issue with py.path.svnwc().versioned() - (thanks Wouter Vanden Hove) - -* setup/teardown or collection problems now show as ERRORs - or with big "E"'s in the progress lines. they are reported - and counted separately. - -* dist-testing: properly handle test items that get locally - collected but cannot be collected on the remote side - often - due to platform/dependency reasons - -* simplified py.test.mark API - see keyword plugin documentation - -* integrate better with logging: capturing now by default captures - test functions and their immediate setup/teardown in a single stream - -* capsys and capfd funcargs now have a readouterr() and a close() method - (underlyingly py.io.StdCapture/FD objects are used which grew a - readouterr() method as well to return snapshots of captured out/err) - -* make assert-reinterpretation work better with comparisons not - returning bools (reported with numpy from thanks maciej fijalkowski) - -* reworked per-test output capturing into the pytest_iocapture.py plugin - and thus removed capturing code from config object - -* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr) - - -1.0.0b8 (2009-07-22) -==================== - -* pytest_unittest-plugin is now enabled by default - -* introduced pytest_keyboardinterrupt hook and - refined pytest_sessionfinish hooked, added tests. - -* workaround a buggy logging module interaction ("closing already closed - files"). Thanks to Sridhar Ratnakumar for triggering. - -* if plugins use "py.test.importorskip" for importing - a dependency only a warning will be issued instead - of exiting the testing process. - -* many improvements to docs: - - refined funcargs doc , use the term "factory" instead of "provider" - - added a new talk/tutorial doc page - - better download page - - better plugin docstrings - - added new plugins page and automatic doc generation script - -* fixed teardown problem related to partially failing funcarg setups - (thanks MrTopf for reporting), "pytest_runtest_teardown" is now - always invoked even if the "pytest_runtest_setup" failed. - -* tweaked doctest output for docstrings in py modules, - thanks Radomir. - -1.0.0b7 -======= - -* renamed py.test.xfail back to py.test.mark.xfail to avoid - two ways to decorate for xfail - -* re-added py.test.mark decorator for setting keywords on functions - (it was actually documented so removing it was not nice) - -* remove scope-argument from request.addfinalizer() because - request.cached_setup has the scope arg. TOOWTDI. - -* perform setup finalization before reporting failures - -* apply modified patches from Andreas Kloeckner to allow - test functions to have no func_code (#22) and to make - "-k" and function keywords work (#20) - -* apply patch from Daniel Peolzleithner (issue #23) - -* resolve issue #18, multiprocessing.Manager() and - redirection clash - -* make __name__ == "__channelexec__" for remote_exec code - -1.0.0b3 (2009-06-19) -==================== - -* plugin classes are removed: one now defines - hooks directly in conftest.py or global pytest_*.py - files. - -* added new pytest_namespace(config) hook that allows - to inject helpers directly to the py.test.* namespace. - -* documented and refined many hooks - -* added new style of generative tests via - pytest_generate_tests hook that integrates - well with function arguments. - - -1.0.0b1 -======= - -* introduced new "funcarg" setup method, - see doc/test/funcarg.txt - -* introduced plugin architecture and many - new py.test plugins, see - doc/test/plugins.txt - -* teardown_method is now guaranteed to get - called after a test method has run. - -* new method: py.test.importorskip(mod,minversion) - will either import or call py.test.skip() - -* completely revised internal py.test architecture - -* new py.process.ForkedFunc object allowing to - fork execution of a function to a sub process - and getting a result back. - -XXX lots of things missing here XXX - -0.9.2 -===== - -* refined installation and metadata, created new setup.py, - now based on setuptools/ez_setup (thanks to Ralf Schmitt - for his support). - -* improved the way of making py.* scripts available in - windows environments, they are now added to the - Scripts directory as ".cmd" files. - -* py.path.svnwc.status() now is more complete and - uses xml output from the 'svn' command if available - (Guido Wesdorp) - -* fix for py.path.svn* to work with svn 1.5 - (Chris Lamb) - -* fix path.relto(otherpath) method on windows to - use normcase for checking if a path is relative. - -* py.test's traceback is better parseable from editors - (follows the filenames:LINENO: MSG convention) - (thanks to Osmo Salomaa) - -* fix to javascript-generation, "py.test --runbrowser" - should work more reliably now - -* removed previously accidentally added - py.test.broken and py.test.notimplemented helpers. - -* there now is a py.__version__ attribute - -0.9.1 -===== - -This is a fairly complete list of v0.9.1, which can -serve as a reference for developers. - -* allowing + signs in py.path.svn urls [39106] -* fixed support for Failed exceptions without excinfo in py.test [39340] -* added support for killing processes for Windows (as well as platforms that - support os.kill) in py.misc.killproc [39655] -* added setup/teardown for generative tests to py.test [40702] -* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739] -* fixed problem with calling .remove() on wcpaths of non-versioned files in - py.path [44248] -* fixed some import and inheritance issues in py.test [41480, 44648, 44655] -* fail to run greenlet tests when pypy is available, but without stackless - [45294] -* small fixes in rsession tests [45295] -* fixed issue with 2.5 type representations in py.test [45483, 45484] -* made that internal reporting issues displaying is done atomically in py.test - [45518] -* made that non-existing files are ignored by the py.lookup script [45519] -* improved exception name creation in py.test [45535] -* made that less threads are used in execnet [merge in 45539] -* removed lock required for atomic reporting issue displaying in py.test - [45545] -* removed globals from execnet [45541, 45547] -* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit - get called in 2.5 (py.execnet) [45548] -* fixed bug in joining threads in py.execnet's servemain [45549] -* refactored py.test.rsession tests to not rely on exact output format anymore - [45646] -* using repr() on test outcome [45647] -* added 'Reason' classes for py.test.skip() [45648, 45649] -* killed some unnecessary sanity check in py.test.collect [45655] -* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only - usable by Administrators [45901] -* added support for locking and non-recursive commits to py.path.svnwc [45994] -* locking files in py.execnet to prevent CPython from segfaulting [46010] -* added export() method to py.path.svnurl -* fixed -d -x in py.test [47277] -* fixed argument concatenation problem in py.path.svnwc [49423] -* restore py.test behaviour that it exits with code 1 when there are failures - [49974] -* don't fail on html files that don't have an accompanying .txt file [50606] -* fixed 'utestconvert.py < input' [50645] -* small fix for code indentation in py.code.source [50755] -* fix _docgen.py documentation building [51285] -* improved checks for source representation of code blocks in py.test [51292] -* added support for passing authentication to py.path.svn* objects [52000, - 52001] -* removed sorted() call for py.apigen tests in favour of [].sort() to support - Python 2.3 [52481] +The source document can be found at: https://github.com/pytest-dev/pytest/blob/master/doc/en/changelog.rst diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/CODE_OF_CONDUCT.md b/tests/wpt/web-platform-tests/tools/third_party/pytest/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..f0ca304be4e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at coc@pytest.org. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +The coc@pytest.org address is routed to the following people who can also be +contacted individually: + +- Brianna Laugher ([@pfctdayelise](https://github.com/pfctdayelise)): brianna@laugher.id.au +- Bruno Oliveira ([@nicoddemus](https://github.com/nicoddemus)): nicoddemus@gmail.com +- Florian Bruhin ([@the-compiler](https://github.com/the-compiler)): pytest@the-compiler.org + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst index 57628a34bdf..48ba147b7db 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/CONTRIBUTING.rst @@ -2,11 +2,12 @@ Contribution getting started ============================ -Contributions are highly welcomed and appreciated. Every little help counts, +Contributions are highly welcomed and appreciated. Every little bit of help counts, so do not hesitate! -.. contents:: Contribution links +.. contents:: :depth: 2 + :backlinks: none .. _submitfeedback: @@ -50,7 +51,8 @@ Fix bugs Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_. -:ref:`Talk <contact>` to developers to find out how you can fix specific bugs. +:ref:`Talk <contact>` to developers to find out how you can fix specific bugs. To indicate that you are going +to work on a particular issue, add a comment to that effect on the specific issue. Don't forget to check the issue trackers of your favourite plugins, too! @@ -84,9 +86,40 @@ without using a local copy. This can be convenient for small fixes. $ tox -e docs - The built documentation should be available in the ``doc/en/_build/``. + The built documentation should be available in ``doc/en/_build/html``, + where 'en' refers to the documentation language. + +Pytest has an API reference which in large part is +`generated automatically <https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html>`_ +from the docstrings of the documented items. Pytest uses the +`Sphinx docstring format <https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html>`_. +For example: + +.. code-block:: python + + def my_function(arg: ArgType) -> Foo: + """Do important stuff. + + More detailed info here, in separate paragraphs from the subject line. + Use proper sentences -- start sentences with capital letters and end + with periods. + + Can include annotated documentation: + + :param short_arg: An argument which determines stuff. + :param long_arg: + A long explanation which spans multiple lines, overflows + like this. + :returns: The result. + :raises ValueError: + Detailed information when this can happen. + + .. versionadded:: 6.0 + + Including types into the annotations above is not necessary when + type-hinting is being used (as in this example). + """ - Where 'en' refers to the documentation language. .. _submitplugin: @@ -98,8 +131,6 @@ in repositories living under the ``pytest-dev`` organisations: - `pytest-dev on GitHub <https://github.com/pytest-dev>`_ -- `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_ - All pytest-dev Contributors team members have write access to all contained repositories. Pytest core and plugins are generally developed using `pull requests`_ to respective repositories. @@ -115,20 +146,21 @@ You can submit your plugin by subscribing to the `pytest-dev mail list mail pointing to your existing pytest plugin repository which must have the following: -- PyPI presence with a ``setup.py`` that contains a license, ``pytest-`` +- PyPI presence with packaging metadata that contains a ``pytest-`` prefixed name, version number, authors, short and long description. -- a ``tox.ini`` for running tests using `tox <https://tox.readthedocs.io>`_. +- a `tox configuration <https://tox.readthedocs.io/en/latest/config.html#configuration-discovery>`_ + for running tests using `tox <https://tox.readthedocs.io>`_. -- a ``README.txt`` describing how to use the plugin and on which +- a ``README`` describing how to use the plugin and on which platforms it runs. -- a ``LICENSE.txt`` file or equivalent containing the licensing - information, with matching info in ``setup.py``. +- a ``LICENSE`` file containing the licensing information, with + matching info in its packaging metadata. - an issue tracker for bug reports and enhancement requests. -- a `changelog <http://keepachangelog.com/>`_ +- a `changelog <http://keepachangelog.com/>`_. If no contributor strongly objects and two agree, the repository can then be transferred to the ``pytest-dev`` organisation. @@ -164,18 +196,18 @@ Short version #. Fork the repository. #. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed. -#. Target ``master`` for bugfixes and doc changes. -#. Target ``features`` for new features or functionality changes. -#. Follow **PEP-8** for naming and `black <https://github.com/python/black>`_ for formatting. +#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting. #. Tests are run using ``tox``:: - tox -e linting,py27,py37 + tox -e linting,py37 The test environments above are usually enough to cover most cases locally. -#. Write a ``changelog`` entry: ``changelog/2574.bugfix``, use issue id number - and one of ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or - ``trivial`` for the issue type. +#. Write a ``changelog`` entry: ``changelog/2574.bugfix.rst``, use issue id number + and one of ``feature``, ``improvement``, ``bugfix``, ``doc``, ``deprecation``, + ``breaking``, ``vendor`` or ``trivial`` for the issue type. + + #. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please add yourself to the ``AUTHORS`` file, in alphabetical order. @@ -202,15 +234,11 @@ Here is a simple overview, with pytest-specific bits: $ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git $ cd pytest - # now, to fix a bug create your own branch off "master": + # now, create your own branch off "master": $ git checkout -b your-bugfix-branch-name master - # or to instead add a feature create your own branch off "features": - - $ git checkout -b your-feature-branch-name features - - Given we have "major.minor.micro" version numbers, bugfixes will usually + Given we have "major.minor.micro" version numbers, bug fixes will usually be released in micro releases whereas features will be released in minor releases and incompatible changes in major releases. @@ -237,20 +265,20 @@ Here is a simple overview, with pytest-specific bits: #. Run all the tests - You need to have Python 2.7 and 3.7 available in your system. Now + You need to have Python 3.7 available in your system. Now running tests is as simple as issuing this command:: - $ tox -e linting,py27,py37 + $ tox -e linting,py37 - This command will run tests via the "tox" tool against Python 2.7 and 3.7 + This command will run tests via the "tox" tool against Python 3.7 and also perform "lint" coding-style checks. #. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming. - You can pass different options to ``tox``. For example, to run tests on Python 2.7 and pass options to pytest + You can pass different options to ``tox``. For example, to run tests on Python 3.7 and pass options to pytest (e.g. enter pdb on failure) to pytest you can do:: - $ tox -e py27 -- --pdb + $ tox -e py37 -- --pdb Or to only run tests in a particular test module on Python 3.7:: @@ -259,14 +287,29 @@ Here is a simple overview, with pytest-specific bits: When committing, ``pre-commit`` will re-format the files if necessary. +#. If instead of using ``tox`` you prefer to run the tests directly, then we suggest to create a virtual environment and use + an editable install with the ``testing`` extra:: + + $ python3 -m venv .venv + $ source .venv/bin/activate # Linux + $ .venv/Scripts/activate.bat # Windows + $ pip install -e ".[testing]" + + Afterwards, you can edit the files and run pytest normally:: + + $ pytest testing/test_config.py + + #. Commit and push once your tests pass and you are happy with your change(s):: $ git commit -a -m "<commit message>" $ git push -u -#. Create a new changelog entry in ``changelog``. The file should be named ``<issueid>.<type>``, +#. Create a new changelog entry in ``changelog``. The file should be named ``<issueid>.<type>.rst``, where *issueid* is the number of the issue related to the change and *type* is one of - ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or ``trivial``. + ``feature``, ``improvement``, ``bugfix``, ``doc``, ``deprecation``, ``breaking``, ``vendor`` + or ``trivial``. You may skip creating the changelog entry if the change doesn't affect the + documented behaviour of pytest. #. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order. @@ -276,14 +319,13 @@ Here is a simple overview, with pytest-specific bits: compare: your-branch-name base-fork: pytest-dev/pytest - base: master # if it's a bugfix - base: features # if it's a feature + base: master Writing Tests ----------------------------- +~~~~~~~~~~~~~ -Writing tests for plugins or for pytest itself is often done using the `testdir fixture <https://docs.pytest.org/en/latest/reference.html#testdir>`_, as a "black-box" test. +Writing tests for plugins or for pytest itself is often done using the `testdir fixture <https://docs.pytest.org/en/stable/reference.html#testdir>`_, as a "black-box" test. For example, to ensure a simple test passes you can write: @@ -320,16 +362,121 @@ one file which looks like a good fit. For example, a regression test about a bug should go into ``test_cacheprovider.py``, given that this option is implemented in ``cacheprovider.py``. If in doubt, go ahead and open a PR with your best guess and we can discuss this over the code. - Joining the Development Team ---------------------------- Anyone who has successfully seen through a pull request which did not require any extra work from the development team to merge will themselves gain commit access if they so wish (if we forget to ask please send a friendly -reminder). This does not mean your workflow to contribute changes, +reminder). This does not mean there is any change in your contribution workflow: everyone goes through the same pull-request-and-review process and no-one merges their own pull requests unless already approved. It does however mean you can participate in the development process more fully since you can merge pull requests from other contributors yourself after having reviewed them. + + +Backporting bug fixes for the next patch release +------------------------------------------------ + +Pytest makes feature release every few weeks or months. In between, patch releases +are made to the previous feature release, containing bug fixes only. The bug fixes +usually fix regressions, but may be any change that should reach users before the +next feature release. + +Suppose for example that the latest release was 1.2.3, and you want to include +a bug fix in 1.2.4 (check https://github.com/pytest-dev/pytest/releases for the +actual latest release). The procedure for this is: + +#. First, make sure the bug is fixed the ``master`` branch, with a regular pull + request, as described above. An exception to this is if the bug fix is not + applicable to ``master`` anymore. + +#. ``git checkout origin/1.2.x -b backport-XXXX`` # use the master PR number here + +#. Locate the merge commit on the PR, in the *merged* message, for example: + + nicoddemus merged commit 0f8b462 into pytest-dev:master + +#. ``git cherry-pick -x -m1 REVISION`` # use the revision you found above (``0f8b462``). + +#. Open a PR targeting ``1.2.x``: + + * Prefix the message with ``[1.2.x]``. + * Delete the PR body, it usually contains a duplicate commit message. + + +Who does the backporting +~~~~~~~~~~~~~~~~~~~~~~~~ + +As mentioned above, bugs should first be fixed on ``master`` (except in rare occasions +that a bug only happens in a previous release). So who should do the backport procedure described +above? + +1. If the bug was fixed by a core developer, it is the main responsibility of that core developer + to do the backport. +2. However, often the merge is done by another maintainer, in which case it is nice of them to + do the backport procedure if they have the time. +3. For bugs submitted by non-maintainers, it is expected that a core developer will to do + the backport, normally the one that merged the PR on ``master``. +4. If a non-maintainers notices a bug which is fixed on ``master`` but has not been backported + (due to maintainers forgetting to apply the *needs backport* label, or just plain missing it), + they are also welcome to open a PR with the backport. The procedure is simple and really + helps with the maintenance of the project. + +All the above are not rules, but merely some guidelines/suggestions on what we should expect +about backports. + +Handling stale issues/PRs +------------------------- + +Stale issues/PRs are those where pytest contributors have asked for questions/changes +and the authors didn't get around to answer/implement them yet after a somewhat long time, or +the discussion simply died because people seemed to lose interest. + +There are many reasons why people don't answer questions or implement requested changes: +they might get busy, lose interest, or just forget about it, +but the fact is that this is very common in open source software. + +The pytest team really appreciates every issue and pull request, but being a high-volume project +with many issues and pull requests being submitted daily, we try to reduce the number of stale +issues and PRs by regularly closing them. When an issue/pull request is closed in this manner, +it is by no means a dismissal of the topic being tackled by the issue/pull request, but it +is just a way for us to clear up the queue and make the maintainers' work more manageable. Submitters +can always reopen the issue/pull request in their own time later if it makes sense. + +When to close +~~~~~~~~~~~~~ + +Here are a few general rules the maintainers use to decide when to close issues/PRs because +of lack of inactivity: + +* Issues labeled ``question`` or ``needs information``: closed after 14 days inactive. +* Issues labeled ``proposal``: closed after six months inactive. +* Pull requests: after one month, consider pinging the author, update linked issue, or consider closing. For pull requests which are nearly finished, the team should consider finishing it up and merging it. + +The above are **not hard rules**, but merely **guidelines**, and can be (and often are!) reviewed on a case-by-case basis. + +Closing pull requests +~~~~~~~~~~~~~~~~~~~~~ + +When closing a Pull Request, it needs to be acknowledge the time, effort, and interest demonstrated by the person which submitted it. As mentioned previously, it is not the intent of the team to dismiss stalled pull request entirely but to merely to clear up our queue, so a message like the one below is warranted when closing a pull request that went stale: + + Hi <contributor>, + + First of all we would like to thank you for your time and effort on working on this, the pytest team deeply appreciates it. + + We noticed it has been awhile since you have updated this PR, however. pytest is a high activity project, with many issues/PRs being opened daily, so it is hard for us maintainers to track which PRs are ready for merging, for review, or need more attention. + + So for those reasons we think it is best to close the PR for now, but with the only intention to cleanup our queue, it is by no means a rejection of your changes. We still encourage you to re-open this PR (it is just a click of a button away) when you are ready to get back to it. + + Again we appreciate your time for working on this, and hope you might get back to this at a later time! + + <bye> + +Closing Issues +-------------- + +When a pull request is submitted to fix an issue, add text like ``closes #XYZW`` to the PR description and/or commits (where ``XYZW`` is the issue number). See the `GitHub docs <https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword>`_ for more information. + +When an issue is due to user error (e.g. misunderstanding of a functionality), please politely explain to the user why the issue raised is really a non-issue and ask them to close the issue if they have no further questions. If the original requestor is unresponsive, the issue will be handled as described in the section `Handling stale issues/PRs`_ above. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/HOWTORELEASE.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/HOWTORELEASE.rst deleted file mode 100644 index b37a245c0be..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/HOWTORELEASE.rst +++ /dev/null @@ -1,48 +0,0 @@ -Release Procedure ------------------ - -Our current policy for releasing is to aim for a bugfix every few weeks and a minor release every 2-3 months. The idea -is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence -taking a lot of time to make a new one. - -.. important:: - - pytest releases must be prepared on **Linux** because the docs and examples expect - to be executed in that platform. - -#. Create a branch ``release-X.Y.Z`` with the version for the release. - - * **patch releases**: from the latest ``master``; - - * **minor releases**: from the latest ``features``; then merge with the latest ``master``; - - Ensure your are in a clean work tree. - -#. Using ``tox``, generate docs, changelog, announcements:: - - $ tox -e release -- <VERSION> - - This will generate a commit with all the changes ready for pushing. - -#. Open a PR for this branch targeting ``master``. - -#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag:: - - git tag <VERSION> - git push git@github.com:pytest-dev/pytest.git <VERSION> - - Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_. - -#. Merge the PR into ``master``. - -#. Send an email announcement with the contents from:: - - doc/en/announce/release-<VERSION>.rst - - To the following mailing lists: - - * pytest-dev@python.org (all releases) - * python-announce-list@python.org (all releases) - * testing-in-python@lists.idyll.org (only major/minor releases) - - And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/OPENCOLLECTIVE.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/OPENCOLLECTIVE.rst new file mode 100644 index 00000000000..8c1c90281e4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/OPENCOLLECTIVE.rst @@ -0,0 +1,44 @@ +============== +OpenCollective +============== + +pytest has a collective setup at `OpenCollective`_. This document describes how the core team manages +OpenCollective-related activities. + +What is it +========== + +Open Collective is an online funding platform for open and transparent communities. +It provides tools to raise money and share your finances in full transparency. + +It is the platform of choice for individuals and companies that want to make one-time or +monthly donations directly to the project. + +Funds +===== + +The OpenCollective funds donated to pytest will be used to fund overall maintenance, +local sprints, merchandising (stickers to distribute in conferences for example), and future +gatherings of pytest developers (sprints). + +`Core contributors`_ which are contributing on a continuous basis are free to submit invoices +to bill maintenance hours using the platform. How much each contributor should request is still an +open question, but we should use common sense and trust in the contributors, most of which know +themselves in-person. A good rule of thumb is to bill the same amount as monthly payments +contributors which participate in the `Tidelift`_ subscription. If in doubt, just ask. + +Admins +====== + +A few people have admin access to the OpenCollective dashboard to make changes. Those people +are part of the `@pytest-dev/opencollective-admins`_ team. + +`Core contributors`_ interested in helping out with OpenCollective maintenance are welcome! We don't +expect much work here other than the occasional approval of expenses from other core contributors. +Just drop a line to one of the `@pytest-dev/opencollective-admins`_ or use the mailing list. + + +.. _`OpenCollective`: https://opencollective.com/pytest +.. _`Tidelift`: https://tidelift.com +.. _`core contributors`: https://github.com/orgs/pytest-dev/teams/core/members +.. _`@pytest-dev/opencollective-admins`: https://github.com/orgs/pytest-dev/teams/opencollective-admins/members diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/PKG-INFO b/tests/wpt/web-platform-tests/tools/third_party/pytest/PKG-INFO new file mode 100644 index 00000000000..9a387bfaf2c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/PKG-INFO @@ -0,0 +1,192 @@ +Metadata-Version: 2.1 +Name: pytest +Version: 6.1.1 +Summary: pytest: simple powerful testing with Python +Home-page: https://docs.pytest.org/en/latest/ +Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others +License: MIT +Project-URL: Source, https://github.com/pytest-dev/pytest +Project-URL: Tracker, https://github.com/pytest-dev/pytest/issues +Description: .. image:: https://docs.pytest.org/en/stable/_static/pytest1.png + :target: https://docs.pytest.org/en/stable/ + :align: center + :alt: pytest + + + ------ + + .. image:: https://img.shields.io/pypi/v/pytest.svg + :target: https://pypi.org/project/pytest/ + + .. image:: https://img.shields.io/conda/vn/conda-forge/pytest.svg + :target: https://anaconda.org/conda-forge/pytest + + .. image:: https://img.shields.io/pypi/pyversions/pytest.svg + :target: https://pypi.org/project/pytest/ + + .. image:: https://codecov.io/gh/pytest-dev/pytest/branch/master/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pytest + :alt: Code coverage Status + + .. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master + :target: https://travis-ci.org/pytest-dev/pytest + + .. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg + :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain + + .. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + + .. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg + :target: https://www.codetriage.com/pytest-dev/pytest + + .. image:: https://readthedocs.org/projects/pytest/badge/?version=latest + :target: https://pytest.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + + The ``pytest`` framework makes it easy to write small tests, yet + scales to support complex functional testing for applications and libraries. + + An example of a simple test: + + .. code-block:: python + + # content of test_sample.py + def inc(x): + return x + 1 + + + def test_answer(): + assert inc(3) == 5 + + + To execute it:: + + $ pytest + ============================= test session starts ============================= + collected 1 items + + test_sample.py F + + ================================== FAILURES =================================== + _________________________________ test_answer _________________________________ + + def test_answer(): + > assert inc(3) == 5 + E assert 4 == 5 + E + where 4 = inc(3) + + test_sample.py:5: AssertionError + ========================== 1 failed in 0.04 seconds =========================== + + + Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <https://docs.pytest.org/en/stable/getting-started.html#our-first-test-run>`_ for more examples. + + + Features + -------- + + - Detailed info on failing `assert statements <https://docs.pytest.org/en/stable/assert.html>`_ (no need to remember ``self.assert*`` names) + + - `Auto-discovery + <https://docs.pytest.org/en/stable/goodpractices.html#python-test-discovery>`_ + of test modules and functions + + - `Modular fixtures <https://docs.pytest.org/en/stable/fixture.html>`_ for + managing small or parametrized long-lived test resources + + - Can run `unittest <https://docs.pytest.org/en/stable/unittest.html>`_ (or trial), + `nose <https://docs.pytest.org/en/stable/nose.html>`_ test suites out of the box + + - Python 3.5+ and PyPy3 + + - Rich plugin architecture, with over 850+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community + + + Documentation + ------------- + + For full documentation, including installation, tutorials and PDF documents, please see https://docs.pytest.org/en/stable/. + + + Bugs/Requests + ------------- + + Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features. + + + Changelog + --------- + + Consult the `Changelog <https://docs.pytest.org/en/stable/changelog.html>`__ page for fixes and enhancements of each version. + + + Support pytest + -------------- + + `Open Collective`_ is an online funding platform for open and transparent communities. + It provides tools to raise money and share your finances in full transparency. + + It is the platform of choice for individuals and companies that want to make one-time or + monthly donations directly to the project. + + See more details in the `pytest collective`_. + + .. _Open Collective: https://opencollective.com + .. _pytest collective: https://opencollective.com/pytest + + + pytest for enterprise + --------------------- + + Available as part of the Tidelift Subscription. + + The maintainers of pytest and thousands of other packages are working with Tidelift to deliver commercial support and + maintenance for the open source dependencies you use to build your applications. + Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. + + `Learn more. <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_ + + Security + ^^^^^^^^ + + pytest has never been associated with a security vulnerability, but in any case, to report a + security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_. + Tidelift will coordinate the fix and disclosure. + + + License + ------- + + Copyright Holger Krekel and others, 2004-2020. + + Distributed under the terms of the `MIT`_ license, pytest is free and open source software. + + .. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE + +Keywords: test,unittest +Platform: unix +Platform: linux +Platform: osx +Platform: cygwin +Platform: win32 +Classifier: Development Status :: 6 - Mature +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Utilities +Requires-Python: >=3.5 +Description-Content-Type: text/x-rst +Provides-Extra: checkqa_mypy +Provides-Extra: testing diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/README.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/README.rst index 69408978703..057278a926b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/README.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/README.rst @@ -1,5 +1,5 @@ -.. image:: https://docs.pytest.org/en/latest/_static/pytest1.png - :target: https://docs.pytest.org/en/latest/ +.. image:: https://docs.pytest.org/en/stable/_static/pytest1.png + :target: https://docs.pytest.org/en/stable/ :align: center :alt: pytest @@ -22,15 +22,19 @@ .. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master :target: https://travis-ci.org/pytest-dev/pytest -.. image:: https://dev.azure.com/pytest-dev/pytest/_apis/build/status/pytest-CI?branchName=master - :target: https://dev.azure.com/pytest-dev/pytest +.. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg + :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain .. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/python/black + :target: https://github.com/psf/black .. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg :target: https://www.codetriage.com/pytest-dev/pytest +.. image:: https://readthedocs.org/projects/pytest/badge/?version=latest + :target: https://pytest.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + The ``pytest`` framework makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries. @@ -67,33 +71,33 @@ To execute it:: ========================== 1 failed in 0.04 seconds =========================== -Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <https://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples. +Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <https://docs.pytest.org/en/stable/getting-started.html#our-first-test-run>`_ for more examples. Features -------- -- Detailed info on failing `assert statements <https://docs.pytest.org/en/latest/assert.html>`_ (no need to remember ``self.assert*`` names); +- Detailed info on failing `assert statements <https://docs.pytest.org/en/stable/assert.html>`_ (no need to remember ``self.assert*`` names) - `Auto-discovery - <https://docs.pytest.org/en/latest/goodpractices.html#python-test-discovery>`_ - of test modules and functions; + <https://docs.pytest.org/en/stable/goodpractices.html#python-test-discovery>`_ + of test modules and functions -- `Modular fixtures <https://docs.pytest.org/en/latest/fixture.html>`_ for - managing small or parametrized long-lived test resources; +- `Modular fixtures <https://docs.pytest.org/en/stable/fixture.html>`_ for + managing small or parametrized long-lived test resources -- Can run `unittest <https://docs.pytest.org/en/latest/unittest.html>`_ (or trial), - `nose <https://docs.pytest.org/en/latest/nose.html>`_ test suites out of the box; +- Can run `unittest <https://docs.pytest.org/en/stable/unittest.html>`_ (or trial), + `nose <https://docs.pytest.org/en/stable/nose.html>`_ test suites out of the box -- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested); +- Python 3.5+ and PyPy3 -- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community; +- Rich plugin architecture, with over 850+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community Documentation ------------- -For full documentation, including installation, tutorials and PDF documents, please see https://docs.pytest.org/en/latest/. +For full documentation, including installation, tutorials and PDF documents, please see https://docs.pytest.org/en/stable/. Bugs/Requests @@ -105,25 +109,39 @@ Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issue Changelog --------- -Consult the `Changelog <https://docs.pytest.org/en/latest/changelog.html>`__ page for fixes and enhancements of each version. +Consult the `Changelog <https://docs.pytest.org/en/stable/changelog.html>`__ page for fixes and enhancements of each version. Support pytest -------------- -You can support pytest by obtaining a `Tideflift subscription`_. +`Open Collective`_ is an online funding platform for open and transparent communities. +It provides tools to raise money and share your finances in full transparency. + +It is the platform of choice for individuals and companies that want to make one-time or +monthly donations directly to the project. + +See more details in the `pytest collective`_. + +.. _Open Collective: https://opencollective.com +.. _pytest collective: https://opencollective.com/pytest + -Tidelift gives software development teams a single source for purchasing and maintaining their software, -with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools. +pytest for enterprise +--------------------- +Available as part of the Tidelift Subscription. -.. _`Tideflift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme +The maintainers of pytest and thousands of other packages are working with Tidelift to deliver commercial support and +maintenance for the open source dependencies you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. +`Learn more. <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_ Security ^^^^^^^^ -pytest has never been associated with a security vunerability, but in any case, to report a +pytest has never been associated with a security vulnerability, but in any case, to report a security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_. Tidelift will coordinate the fix and disclosure. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/RELEASING.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/RELEASING.rst new file mode 100644 index 00000000000..9ff95be92ed --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/RELEASING.rst @@ -0,0 +1,135 @@ +Release Procedure +----------------- + +Our current policy for releasing is to aim for a bug-fix release every few weeks and a minor release every 2-3 months. The idea +is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence +taking a lot of time to make a new one. + +The git commands assume the following remotes are setup: + +* ``origin``: your own fork of the repository. +* ``upstream``: the ``pytest-dev/pytest`` official repository. + +Preparing: Automatic Method +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We have developed an automated workflow for releases, that uses GitHub workflows and is triggered +by opening an issue. + +Bug-fix releases +^^^^^^^^^^^^^^^^ + +A bug-fix release is always done from a maintenance branch, so for example to release bug-fix +``5.1.2``, open a new issue and add this comment to the body:: + + @pytestbot please prepare release from 5.1.x + +Where ``5.1.x`` is the maintenance branch for the ``5.1`` series. + +The automated workflow will publish a PR for a branch ``release-5.1.2`` +and notify it as a comment in the issue. + +Minor releases +^^^^^^^^^^^^^^ + +1. Create a new maintenance branch from ``master``:: + + git fetch --all + git branch 5.2.x upstream/master + git push upstream 5.2.x + +2. Open a new issue and add this comment to the body:: + + @pytestbot please prepare release from 5.2.x + +The automated workflow will publish a PR for a branch ``release-5.2.0`` and +notify it as a comment in the issue. + +Major and release candidates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. Create a new maintenance branch from ``master``:: + + git fetch --all + git branch 6.0.x upstream/master + git push upstream 6.0.x + +2. For a **major release**, open a new issue and add this comment in the body:: + + @pytestbot please prepare major release from 6.0.x + + For a **release candidate**, the comment must be (TODO: `#7551 <https://github.com/pytest-dev/pytest/issues/7551>`__):: + + @pytestbot please prepare release candidate from 6.0.x + +The automated workflow will publish a PR for a branch ``release-6.0.0`` and +notify it as a comment in the issue. + +At this point on, this follows the same workflow as other maintenance branches: bug-fixes are merged +into ``master`` and ported back to the maintenance branch, even for release candidates. + +**A note about release candidates** + +During release candidates we can merge small improvements into +the maintenance branch before releasing the final major version, however we must take care +to avoid introducing big changes at this stage. + +Preparing: Manual Method +~~~~~~~~~~~~~~~~~~~~~~~~ + +**Important**: pytest releases must be prepared on **Linux** because the docs and examples expect +to be executed on that platform. + +To release a version ``MAJOR.MINOR.PATCH``, follow these steps: + +#. For major and minor releases, create a new branch ``MAJOR.MINOR.x`` from + ``upstream/master`` and push it to ``upstream``. + +#. Create a branch ``release-MAJOR.MINOR.PATCH`` from the ``MAJOR.MINOR.x`` branch. + + Ensure your are updated and in a clean working tree. + +#. Using ``tox``, generate docs, changelog, announcements:: + + $ tox -e release -- MAJOR.MINOR.PATCH + + This will generate a commit with all the changes ready for pushing. + +#. Open a PR for the ``release-MAJOR.MINOR.PATCH`` branch targeting ``MAJOR.MINOR.x``. + + +Releasing +~~~~~~~~~ + +Both automatic and manual processes described above follow the same steps from this point onward. + +#. After all tests pass and the PR has been approved, tag the release commit + in the ``release-MAJOR.MINOR.PATCH`` branch and push it. This will publish to PyPI:: + + git fetch --all + git tag MAJOR.MINOR.PATCH upstream/release-MAJOR.MINOR.PATCH + git push git@github.com:pytest-dev/pytest.git MAJOR.MINOR.PATCH + + Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_. + +#. Merge the PR. + +#. Cherry-pick the CHANGELOG / announce files to the ``master`` branch:: + + git fetch --all --prune + git checkout origin/master -b cherry-pick-release + git cherry-pick -x -m1 upstream/MAJOR.MINOR.x + +#. Open a PR for ``cherry-pick-release`` and merge it once CI passes. No need to wait for approvals if there were no conflicts on the previous step. + +#. Send an email announcement with the contents from:: + + doc/en/announce/release-<VERSION>.rst + + To the following mailing lists: + + * pytest-dev@python.org (all releases) + * python-announce-list@python.org (all releases) + * testing-in-python@lists.idyll.org (only major/minor releases) + + And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst index f7081817dfd..b18f4793f81 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/TIDELIFT.rst @@ -12,6 +12,9 @@ Tidelift aims to make Open Source sustainable by offering subscriptions to compa on Open Source packages. This subscription allows it to pay maintainers of those Open Source packages to aid sustainability of the work. +It is the perfect platform for companies that want to support Open Source packages and at the same +time obtain assurances regarding maintenance, quality and security. + Funds ===== @@ -21,7 +24,6 @@ members of the `contributors team`_ interested in receiving funding. The current list of contributors receiving funding are: * `@asottile`_ -* `@blueyed`_ * `@nicoddemus`_ Contributors interested in receiving a part of the funds just need to submit a PR adding their @@ -53,5 +55,4 @@ funds. Just drop a line to one of the `@pytest-dev/tidelift-admins`_ or use the .. _`agreement`: https://tidelift.com/docs/lifting/agreement .. _`@asottile`: https://github.com/asottile -.. _`@blueyed`: https://github.com/blueyed .. _`@nicoddemus`: https://github.com/nicoddemus diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/azure-pipelines.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/azure-pipelines.yml deleted file mode 100644 index 5184610fd18..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/azure-pipelines.yml +++ /dev/null @@ -1,107 +0,0 @@ -trigger: -- master -- features - -variables: - PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv" - python.needs_vc: False - COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage" - COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc" - PYTEST_COVERAGE: '0' - -jobs: - -- job: 'Test' - pool: - vmImage: "vs2017-win2016" - strategy: - matrix: - py27: - python.version: '2.7' - tox.env: 'py27' - py27-nobyte-lsof-numpy: - python.version: '2.7' - tox.env: 'py27-lsof-nobyte-numpy' - # Coverage for: - # - test_supports_breakpoint_module_global - # - test_terminal_reporter_writer_attr (without xdist) - # - "if write" branch in _pytest.assertion.rewrite - # - numpy - # - pytester's LsofFdLeakChecker (being skipped) - PYTEST_COVERAGE: '1' - py27-twisted: - python.version: '2.7' - tox.env: 'py27-twisted' - python.needs_vc: True - py27-pluggymaster-xdist: - python.version: '2.7' - tox.env: 'py27-pluggymaster-xdist' - # Coverage for: - # - except-IOError in _attempt_to_close_capture_file for py2. - # Also seen with py27-nobyte (using xdist), and py27-xdist. - # But no exception with py27-pexpect,py27-twisted,py27-numpy. - PYTEST_COVERAGE: '1' - # -- pypy2 and pypy3 are disabled for now: #5279 -- - # pypy: - # python.version: 'pypy2' - # tox.env: 'pypy' - # pypy3: - # python.version: 'pypy3' - # tox.env: 'pypy3' - py35-xdist: - python.version: '3.5' - tox.env: 'py35-xdist' - # Coverage for: - # - test_supports_breakpoint_module_global - PYTEST_COVERAGE: '1' - py36-xdist: - python.version: '3.6' - tox.env: 'py36-xdist' - py37: - python.version: '3.7' - tox.env: 'py37' - # Coverage for: - # - _py36_windowsconsoleio_workaround (with py36+) - # - test_request_garbage (no xdist) - PYTEST_COVERAGE: '1' - py37-linting/docs/doctesting: - python.version: '3.7' - tox.env: 'linting,docs,doctesting' - py37-twisted/numpy: - python.version: '3.7' - tox.env: 'py37-twisted,py37-numpy' - py37-pluggymaster-xdist: - python.version: '3.7' - tox.env: 'py37-pluggymaster-xdist' - maxParallel: 10 - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '$(python.version)' - architecture: 'x64' - - - script: choco install vcpython27 - condition: eq(variables['python.needs_vc'], True) - displayName: 'Install VC for py27' - - - script: python -m pip install tox - displayName: 'Install tox' - - - script: | - call scripts/setup-coverage-vars.bat || goto :eof - python -m tox -e $(tox.env) - displayName: 'Run tests' - - - task: PublishTestResults@2 - inputs: - testResultsFiles: 'build/test-results/$(tox.env).xml' - testRunTitle: '$(tox.env)' - condition: succeededOrFailed() - - - script: call scripts\upload-coverage.bat - displayName: 'Report and upload coverage' - condition: eq(variables['PYTEST_COVERAGE'], '1') - env: - CODECOV_TOKEN: $(CODECOV_TOKEN) - PYTEST_CODECOV_NAME: $(tox.env) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench.py index 468ef521957..c40fc8636c0 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import sys if __name__ == "__main__": @@ -7,7 +6,7 @@ if __name__ == "__main__": import pstats script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"] - stats = cProfile.run("pytest.cmdline.main(%r)" % script, "prof") + cProfile.run("pytest.cmdline.main(%r)" % script, "prof") p = pstats.Stats("prof") p.strip_dirs() p.sort_stats("cumulative") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py index 297637bad0d..335733df72b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/bench_argcomplete.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # 10000 iterations, just for relative comparison # 2.7.5 3.3.2 # FilesCompleter 75.1109 69.2116 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/empty.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/empty.py index bfda88235de..4e7371b6f80 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/empty.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/empty.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- for i in range(1000): exec("def test_func_%d(): pass" % i) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/manyparam.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/manyparam.py index c47e25f5182..1226c73bd9c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/manyparam.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/manyparam.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/skip.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/skip.py index 29e03e54fc6..f0c9d1ddbef 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/skip.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/bench/skip.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from six.moves import range - import pytest SKIP = True diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/changelog/README.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/changelog/README.rst index e471409b023..6d026f57ef3 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/changelog/README.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/changelog/README.rst @@ -1,21 +1,24 @@ This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted text that will be added to the next ``CHANGELOG``. -The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users +The ``CHANGELOG`` will be read by **users**, so this description should be aimed to pytest users instead of describing internal changes which are only relevant to the developers. -Make sure to use full sentences with correct case and punctuation, for example:: +Make sure to use full sentences in the **past or present tense** and use punctuation, examples:: - Fix issue with non-ascii messages from the ``warnings`` module. + Improved verbose diff output with sequences. + + Terminal summary statistics now use multiple colors. Each file should be named like ``<ISSUE>.<TYPE>.rst``, where ``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of: * ``feature``: new user facing features, like new command-line options and new behavior. -* ``bugfix``: fixes a reported bug. +* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc). +* ``bugfix``: fixes a bug. * ``doc``: documentation improvement, like rewording an entire session or adding missing docs. * ``deprecation``: feature deprecation. -* ``removal``: feature removal. +* ``breaking``: a change which may break existing suites, such as feature removal or behavior change. * ``vendor``: changes in packages vendored in pytest. * ``trivial``: fixing a small typo or internal change that might be noteworthy. @@ -28,6 +31,7 @@ changelog using that instead. If you are not sure what issue type to use, don't hesitate to ask in your PR. ``towncrier`` preserves multiple paragraphs and formatting (code blocks, lists, and so on), but for entries -other than ``features`` it is usually better to stick to a single paragraph to keep it concise. You can install -``towncrier`` and then run ``towncrier --draft`` -if you want to get a preview of how your change will look in the final release notes. +other than ``features`` it is usually better to stick to a single paragraph to keep it concise. + +You can also run ``tox -e docs`` to build the documentation +with the draft changelog (``doc/en/_build/html/changelog.html``) if you want to get a preview of how your change will look in the final release notes. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/codecov.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/codecov.yml index a0a308588e2..f1cc8697338 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/codecov.yml +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/codecov.yml @@ -1,7 +1,6 @@ +# reference: https://docs.codecov.io/docs/codecovyml-reference coverage: status: - project: true patch: true - changes: true - -comment: off + project: false +comment: false diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/Makefile b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/Makefile index 341a5cb8524..1cffbd463d8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/Makefile +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/Makefile @@ -1,154 +1,39 @@ -# Makefile for Sphinx documentation +# Minimal makefile for Sphinx documentation # -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . BUILDDIR = _build -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + REGENDOC_ARGS := \ --normalize "/[ \t]+\n/\n/" \ --normalize "~\$$REGENDOC_TMPDIR~/home/sweet/project~" \ --normalize "~/path/to/example~/home/sweet/project~" \ - --normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \ + --normalize "/in \d.\d\ds/in 0.12s/" \ --normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \ --normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \ - --normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \ --normalize "@py-(\d+)\\.[^ ,]+@py-\1.x.y@" \ --normalize "@pluggy-(\d+)\\.[.\d,]+@pluggy-\1.x.y@" \ --normalize "@hypothesis-(\d+)\\.[.\d,]+@hypothesis-\1.x.y@" \ --normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@" -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - - -help: - @echo "Please use \`make <target>' where <target> is one of" - @echo " html to make standalone HTML files" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " showtarget to show the pytest.org target directory" - @echo " install to install docs to pytest.org/SITETARGET" - @echo " install-ldf to install the doc pdf to pytest.org/SITETARGET" - @echo " regen to regenerate pytest examples using the installed pytest" - @echo " linkcheck to check all external links for integrity" - -clean: - -rm -rf $(BUILDDIR)/* - regen: REGENDOC_FILES:=*.rst */*.rst regen: PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS="-pno:hypothesis -Wignore::pytest.PytestUnknownMarkWarning" COLUMNS=76 regendoc --update ${REGENDOC_FILES} ${REGENDOC_ARGS} -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/pytest.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pytest.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/pytest" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pytest" - @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." - -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." - -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." - -texinfo: - mkdir -p $(BUILDDIR)/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: - mkdir -p $(BUILDDIR)/texinfo - $(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." +.PHONY: regen diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html index 0e088d67ef3..4522eb2dec9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html @@ -4,12 +4,15 @@ <li><a href="{{ pathto('index') }}">Home</a></li> <li><a href="{{ pathto('getting-started') }}">Install</a></li> <li><a href="{{ pathto('contents') }}">Contents</a></li> - <li><a href="{{ pathto('reference') }}">Reference</a></li> + <li><a href="{{ pathto('reference') }}">API Reference</a></li> <li><a href="{{ pathto('example/index') }}">Examples</a></li> <li><a href="{{ pathto('customize') }}">Customize</a></li> <li><a href="{{ pathto('changelog') }}">Changelog</a></li> <li><a href="{{ pathto('contributing') }}">Contributing</a></li> <li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li> + <li><a href="{{ pathto('py27-py34-deprecation') }}">Python 2.7 and 3.4 Support</a></li> + <li><a href="{{ pathto('sponsor') }}">Sponsor</a></li> + <li><a href="{{ pathto('tidelift') }}">pytest for Enterprise</a></li> <li><a href="{{ pathto('license') }}">License</a></li> <li><a href="{{ pathto('contact') }}">Contact Channels</a></li> </ul> @@ -18,3 +21,7 @@ <hr> {{ toc }} {%- endif %} + +<hr> +<a href="{{ pathto('genindex') }}">Index</a> +<hr> diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/layout.html b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/layout.html index 2fc8e2a7fb4..f7096eaaa5e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/layout.html +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/layout.html @@ -1,20 +1,52 @@ -{% extends "!layout.html" %} -{% block header %} - {{super()}} +{# + + Copied from: + + https://raw.githubusercontent.com/pallets/pallets-sphinx-themes/b0c6c41849b4e15cbf62cc1d95c05ef2b3e155c8/src/pallets_sphinx_themes/themes/pocoo/layout.html + + And removed the warning version (see #7331). + +#} + +{% extends "basic/layout.html" %} + +{% set metatags %} + {{- metatags }} + <meta name="viewport" content="width=device-width, initial-scale=1"> +{%- endset %} + +{% block extrahead %} + {%- if page_canonical_url %} + <link rel="canonical" href="{{ page_canonical_url }}"> + {%- endif %} + <script>DOCUMENTATION_OPTIONS.URL_ROOT = '{{ url_root }}';</script> + {{ super() }} +{%- endblock %} + +{% block sidebarlogo %} + {% if pagename != "index" or theme_index_sidebar_logo %} + {{ super() }} + {% endif %} {% endblock %} -{% block footer %} -{{ super() }} -<script type="text/javascript"> - var _gaq = _gaq || []; - _gaq.push(['_setAccount', 'UA-7597274-13']); - _gaq.push(['_trackPageview']); +{% block relbar2 %}{% endblock %} - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); +{% block sidebar2 %} + <span id="sidebar-top"></span> + {{- super() }} +{%- endblock %} -</script> +{% block footer %} + {{ super() }} + {%- if READTHEDOCS and not readthedocs_docsearch %} + <script> + if (typeof READTHEDOCS_DATA !== 'undefined') { + if (!READTHEDOCS_DATA.features) { + READTHEDOCS_DATA.features = {}; + } + READTHEDOCS_DATA.features.docsearch_disabled = true; + } + </script> + {%- endif %} + {{ js_tag("_static/version_warning_offset.js") }} {% endblock %} diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/relations.html b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/relations.html index 3bbcde85bb4..3bbcde85bb4 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/relations.html +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/relations.html diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html new file mode 100644 index 00000000000..e98ad4ed905 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html @@ -0,0 +1,15 @@ +{# + basic/searchbox.html with heading removed. +#} +{%- if pagename != "search" and builder != "singlehtml" %} +<div id="searchbox" style="display: none" role="search"> + <div class="searchformwrapper"> + <form class="search" action="{{ pathto('search') }}" method="get"> + <input type="text" name="q" aria-labelledby="searchlabel" + placeholder="Search"/> + <input type="submit" value="{{ _('Go') }}" /> + </form> + </div> +</div> +<script type="text/javascript">$('#searchbox').show(0);</script> +{%- endif %} diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/.gitignore b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/.gitignore deleted file mode 100644 index 66b6e4c2f3b..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.pyc -*.pyo -.DS_Store diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/LICENSE b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/LICENSE deleted file mode 100644 index 8daab7ee6ef..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/LICENSE +++ /dev/null @@ -1,37 +0,0 @@ -Copyright (c) 2010 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, with or -without modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/README b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/README deleted file mode 100644 index b3292bdff8e..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/README +++ /dev/null @@ -1,31 +0,0 @@ -Flask Sphinx Styles -=================== - -This repository contains sphinx styles for Flask and Flask related -projects. To use this style in your Sphinx documentation, follow -this guide: - -1. put this folder as _themes into your docs folder. Alternatively - you can also use git submodules to check out the contents there. -2. add this to your conf.py: - - sys.path.append(os.path.abspath('_themes')) - html_theme_path = ['_themes'] - html_theme = 'flask' - -The following themes exist: - -- 'flask' - the standard flask documentation theme for large - projects -- 'flask_small' - small one-page theme. Intended to be used by - very small addon libraries for flask. - -The following options exist for the flask_small theme: - - [options] - index_logo = '' filename of a picture in _static - to be used as replacement for the - h1 in the index.rst file. - index_logo_height = 120px height of the index logo - github_fork = '' repository name on github for the - "fork me" badge diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/layout.html b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/layout.html deleted file mode 100644 index 19c43fbbefc..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/layout.html +++ /dev/null @@ -1,24 +0,0 @@ -{%- extends "basic/layout.html" %} -{%- block extrahead %} - {{ super() }} - {% if theme_touch_icon %} - <link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" /> - {% endif %} - <meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9"> -{% endblock %} -{%- block relbar2 %}{% endblock %} -{% block header %} - {{ super() }} - {% if pagename == 'index' %} - <div class=indexwrapper> - {% endif %} -{% endblock %} -{%- block footer %} - <div class="footer"> - © Copyright {{ copyright }}. - Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>. - </div> - {% if pagename == 'index' %} - </div> - {% endif %} -{%- endblock %} diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/static/flasky.css_t b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/static/flasky.css_t deleted file mode 100644 index 6b593da299a..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/static/flasky.css_t +++ /dev/null @@ -1,557 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * :copyright: Copyright 2010 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -{% set page_width = '1020px' %} -{% set sidebar_width = '220px' %} -/* orange of logo is #d67c29 but we use black for links for now */ -{% set link_color = '#000' %} -{% set link_hover_color = '#000' %} -{% set base_font = 'sans-serif' %} -{% set header_font = 'serif' %} - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: {{ base_font }}; - font-size: 17px; - background-color: white; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - width: {{ page_width }}; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ sidebar_width }}; -} - -div.sphinxsidebar { - width: {{ sidebar_width }}; -} - -hr { - border: 0; - border-top: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - width: {{ page_width }}; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -div.related { - display: none; -} - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebar { - font-size: 14px; - line-height: 1.5; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0 0 20px 0; - margin: 0; - text-align: center; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: {{ header_font }}; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: {{ base_font }}; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: {{ link_color }}; - text-decoration: underline; -} - -a:hover { - color: {{ link_hover_color }}; - text-decoration: underline; -} - -a.reference.internal em { - font-style: normal; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: {{ header_font }}; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% else %} -div.indexwrapper div.body h1 { - font-size: 200%; -} -{% endif %} -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #ddd; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition tt.xref, div.admonition a tt { - border-bottom: 1px solid #fafafa; -} - -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - -div.admonition p.admonition-title { - font-family: {{ header_font }}; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt, code { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; - background: #eee; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; - background: #fdfdfd; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td.label { - width: 0px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #eee; - padding: 7px 30px; - margin: 15px -30px; - line-height: 1.3em; -} - -dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid white; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted {{ link_color }}; -} - -a.reference:hover { - border-bottom: 1px solid {{ link_hover_color }}; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted {{ link_color }}; -} - -a.footnote-reference:hover { - border-bottom: 1px solid {{ link_hover_color }}; -} - -a:hover tt { - background: #EEE; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: white; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: white; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a, div.sphinxsidebar ul { - color: white; - } - - div.sphinxsidebar a { - color: #aaa; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.related { - display: block; - margin: 0; - padding: 10px 0 20px 0; - } - - div.related ul, - div.related ul li { - margin: 0; - padding: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} - -/* misc. */ - -.revsys-inline { - display: none!important; -} diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/theme.conf b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/theme.conf deleted file mode 100644 index 372b0028393..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask/theme.conf +++ /dev/null @@ -1,9 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = '' -index_logo_height = 120px -touch_icon = diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask_theme_support.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask_theme_support.py deleted file mode 100644 index c5dcdbe2737..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/_themes/flask_theme_support.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# flasky extensions. flasky pygments style based on tango style -from pygments.style import Style -from pygments.token import Comment -from pygments.token import Error -from pygments.token import Generic -from pygments.token import Keyword -from pygments.token import Literal -from pygments.token import Name -from pygments.token import Number -from pygments.token import Operator -from pygments.token import Other -from pygments.token import Punctuation -from pygments.token import String -from pygments.token import Whitespace - - -class FlaskyStyle(Style): - background_color = "#f8f8f8" - default_style = "" - - styles = { - # No corresponding class for the following: - # Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - Punctuation: "bold #000000", # class: 'p' - # because special names such as Name.Class, Name.Function, etc. - # are not recognized as such later in the parsing, we choose them - # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - Number: "#990000", # class: 'm' - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' - } diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst index 710f431be30..82e2111ed3b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/adopt.rst @@ -24,11 +24,9 @@ The ideal pytest helper - feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents) - does not need to be an expert in every aspect! -`Pytest helpers, sign up here`_! (preferably in February, hard deadline 22 March) +Pytest helpers, sign up here! (preferably in February, hard deadline 22 March) -.. _`Pytest helpers, sign up here`: http://goo.gl/forms/nxqAhqWt1P - The ideal partner project ----------------------------------------- @@ -40,16 +38,14 @@ The ideal partner project - has the support of the core development team, in trying out pytest adoption - has no tests... or 100% test coverage... or somewhere in between! -`Partner projects, sign up here`_! (by 22 March) - +Partner projects, sign up here! (by 22 March) -.. _`Partner projects, sign up here`: http://goo.gl/forms/ZGyqlHiwk3 What does it mean to "adopt pytest"? ----------------------------------------- -There can be many different definitions of "success". Pytest can run many `nose and unittest`_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right? +There can be many different definitions of "success". Pytest can run many nose_ and unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right? Progressive success might look like: @@ -67,12 +63,13 @@ Progressive success might look like: It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies. -.. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest -.. _assert: asserts.html +.. _nose: nose.html +.. _unittest: unittest.html +.. _assert: assert.html .. _pycmd: https://bitbucket.org/hpk42/pycmd/overview .. _`setUp/tearDown methods`: xunit_setup.html .. _fixtures: fixture.html -.. _markers: markers.html +.. _markers: mark.html .. _distributed: xdist.html diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst index 74ad0285509..bda389cd8be 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/index.rst @@ -6,8 +6,33 @@ Release announcements :maxdepth: 2 - release-4.6.11 - release-4.6.10 + release-6.1.1 + release-6.1.0 + release-6.0.2 + release-6.0.1 + release-6.0.0 + release-6.0.0rc1 + release-5.4.3 + release-5.4.2 + release-5.4.1 + release-5.4.0 + release-5.3.5 + release-5.3.4 + release-5.3.3 + release-5.3.2 + release-5.3.1 + release-5.3.0 + release-5.2.4 + release-5.2.3 + release-5.2.2 + release-5.2.1 + release-5.2.0 + release-5.1.3 + release-5.1.2 + release-5.1.1 + release-5.1.0 + release-5.0.1 + release-5.0.0 release-4.6.9 release-4.6.8 release-4.6.7 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst index af745fc59b2..1aaad740a4f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst @@ -7,7 +7,7 @@ see below for summary and detailed lists. A lot of long-deprecated code has been removed, resulting in a much smaller and cleaner implementation. See the new docs with examples here: - http://pytest.org/2.0.0/index.html + http://pytest.org/en/stable/index.html A note on packaging: pytest used to part of the "py" distribution up until version py-1.3.4 but this has changed now: pytest-2.0.0 only @@ -36,12 +36,12 @@ New Features import pytest ; pytest.main(arglist, pluginlist) - see http://pytest.org/2.0.0/usage.html for details. + see http://pytest.org/en/stable/usage.html for details. - new and better reporting information in assert expressions if comparing lists, sequences or strings. - see http://pytest.org/2.0.0/assert.html#newreport + see http://pytest.org/en/stable/assert.html#newreport - new configuration through ini-files (setup.cfg or tox.ini recognized), for example:: @@ -50,7 +50,7 @@ New Features norecursedirs = .hg data* # don't ever recurse in such dirs addopts = -x --pyargs # add these command line options by default - see http://pytest.org/2.0.0/customize.html + see http://pytest.org/en/stable/customize.html - improved standard unittest support. In general py.test should now better be able to run custom unittest.TestCases like twisted trial diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.1.rst index 2f41ef9435e..72401d8098f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.0.1.rst @@ -57,7 +57,7 @@ Changes between 2.0.0 and 2.0.1 - refinements to "collecting" output on non-ttys - refine internal plugin registration and --traceconfig output - introduce a mechanism to prevent/unregister plugins from the - command line, see http://pytest.org/latest/plugins.html#cmdunregister + command line, see http://pytest.org/en/stable/plugins.html#cmdunregister - activate resultlog plugin by default - fix regression wrt yielded tests which due to the collection-before-running semantics were not diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.0.rst index 831548ac2ff..29463ab533e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.1.0.rst @@ -12,7 +12,7 @@ courtesy of Benjamin Peterson. You can now safely use ``assert`` statements in test modules without having to worry about side effects or python optimization ("-OO") options. This is achieved by rewriting assert statements in test modules upon import, using a PEP302 hook. -See http://pytest.org/assert.html#advanced-assertion-introspection for +See https://docs.pytest.org/en/stable/assert.html for detailed information. The work has been partly sponsored by my company, merlinux GmbH. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.0.rst index 20bfe0a1915..0193ffb3465 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.2.0.rst @@ -9,7 +9,7 @@ with these improvements: - new @pytest.mark.parametrize decorator to run tests with different arguments - new metafunc.parametrize() API for parametrizing arguments independently - - see examples at http://pytest.org/latest/example/parametrize.html + - see examples at http://pytest.org/en/stable/example/parametrize.html - NOTE that parametrize() related APIs are still a bit experimental and might change in future releases. @@ -18,7 +18,7 @@ with these improvements: - "-m markexpr" option for selecting tests according to their mark - a new "markers" ini-variable for registering test markers for your project - the new "--strict" bails out with an error if using unregistered markers. - - see examples at http://pytest.org/latest/example/markers.html + - see examples at http://pytest.org/en/stable/example/markers.html * duration profiling: new "--duration=N" option showing the N slowest test execution or setup/teardown calls. This is most useful if you want to @@ -78,7 +78,7 @@ Changes between 2.1.3 and 2.2.0 or through plugin hooks. Also introduce a "--strict" option which will treat unregistered markers as errors allowing to avoid typos and maintain a well described set of markers - for your test suite. See examples at http://pytest.org/latest/mark.html + for your test suite. See examples at http://pytest.org/en/stable/mark.html and its links. - issue50: introduce "-m marker" option to select tests based on markers (this is a stricter and more predictable version of "-k" in that "-m" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst index 061aa025c1e..bdd92a98fde 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst @@ -3,22 +3,22 @@ pytest-2.3: improved fixtures / better unittest integration pytest-2.3 comes with many major improvements for fixture/funcarg management and parametrized testing in Python. It is now easier, more efficient and -more predicatable to re-run the same tests with different fixture +more predictable to re-run the same tests with different fixture instances. Also, you can directly declare the caching "scope" of fixtures so that dependent tests throughout your whole test suite can re-use database or other expensive fixture objects with ease. Lastly, it's possible for fixture functions (formerly known as funcarg factories) to use other fixtures, allowing for a completely modular and -re-useable fixture design. +re-usable fixture design. For detailed info and tutorial-style examples, see: - http://pytest.org/latest/fixture.html + http://pytest.org/en/stable/fixture.html Moreover, there is now support for using pytest fixtures/funcargs with unittest-style suites, see here for examples: - http://pytest.org/latest/unittest.html + http://pytest.org/en/stable/unittest.html Besides, more unittest-test suites are now expected to "simply work" with pytest. @@ -29,11 +29,11 @@ pytest-2.2.4. If you are interested in the precise reasoning (including examples) of the pytest-2.3 fixture evolution, please consult -http://pytest.org/latest/funcarg_compare.html +http://pytest.org/en/stable/funcarg_compare.html For general info on installation and getting started: - http://pytest.org/latest/getting-started.html + http://pytest.org/en/stable/getting-started.html Docs and PDF access as usual at: @@ -94,7 +94,7 @@ Changes between 2.2.4 and 2.3.0 - pluginmanager.register(...) now raises ValueError if the plugin has been already registered or the name is taken -- fix issue159: improve http://pytest.org/latest/faq.html +- fix issue159: improve https://docs.pytest.org/en/6.0.1/faq.html especially with respect to the "magic" history, also mention pytest-django, trial and unittest integration. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.4.rst index e2e8cb143a3..26f76630e84 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.4.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.4.rst @@ -16,7 +16,7 @@ comes with the following fixes and features: - yielded test functions will now have autouse-fixtures active but cannot accept fixtures as funcargs - it's anyway recommended to rather use the post-2.0 parametrize features instead of yield, see: - http://pytest.org/latest/example/parametrize.html + http://pytest.org/en/stable/example/parametrize.html - fix autouse-issue where autouse-fixtures would not be discovered if defined in an a/conftest.py file and tests in a/tests/test_some.py - fix issue226 - LIFO ordering for fixture teardowns diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst index 465dd826ed4..d68780a2440 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst @@ -46,7 +46,7 @@ Changes between 2.3.4 and 2.3.5 - Issue 265 - integrate nose setup/teardown with setupstate so it doesn't try to teardown if it did not setup -- issue 271 - don't write junitxml on slave nodes +- issue 271 - don't write junitxml on worker nodes - Issue 274 - don't try to show full doctest example when doctest does not know the example location diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst index 1b016884146..68297b26c4e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst @@ -7,7 +7,7 @@ from a few supposedly very minor incompatibilities. See below for a full list of details. A few feature highlights: - new yield-style fixtures `pytest.yield_fixture - <http://pytest.org/latest/yieldfixture.html>`_, allowing to use + <http://pytest.org/en/stable/yieldfixture.html>`_, allowing to use existing with-style context managers in fixture functions. - improved pdb support: ``import pdb ; pdb.set_trace()`` now works diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst index 29064e05e6d..bc83fdc122c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst @@ -91,7 +91,7 @@ holger krekel it might be the cause for other finalizers to fail. - fix ordering when mock.patch or other standard decorator-wrappings - are used with test methods. This fixues issue346 and should + are used with test methods. This fixes issue346 and should help with random "xdist" collection failures. Thanks to Ronny Pfannschmidt and Donald Stufft for helping to isolate it. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.1.rst index 22e69a836b9..ff39db2d52d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.1.rst @@ -1,7 +1,7 @@ pytest-2.5.1: fixes and new home page styling =========================================================================== -pytest is a mature Python testing tool with more than a 1000 tests +pytest is a mature Python testing tool with more than 1000 tests against itself, passing on many different interpreters and platforms. The 2.5.1 release maintains the "zero-reported-bugs" promise by fixing diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst index c389f5f5403..edc4da6e19f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst @@ -1,7 +1,7 @@ pytest-2.5.2: fixes =========================================================================== -pytest is a mature Python testing tool with more than a 1000 tests +pytest is a mature Python testing tool with more than 1000 tests against itself, passing on many different interpreters and platforms. The 2.5.2 release fixes a few bugs with two maybe-bugs remaining and diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst index 36b545a28b4..56fbd6cc1e4 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst @@ -1,7 +1,7 @@ pytest-2.6.0: shorter tracebacks, new warning system, test runner compat =========================================================================== -pytest is a mature Python testing tool with more than a 1000 tests +pytest is a mature Python testing tool with more than 1000 tests against itself, passing on many different interpreters and platforms. The 2.6.0 release should be drop-in backward compatible to 2.5.2 and diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.1.rst index fba6f2993a5..7469c488e5f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.1.rst @@ -1,7 +1,7 @@ pytest-2.6.1: fixes and new xfail feature =========================================================================== -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. The 2.6.1 release is drop-in compatible to 2.5.2 and actually fixes some regressions introduced with 2.6.0. It also brings a little feature @@ -32,7 +32,7 @@ Changes 2.6.1 purely the nodeid. The line number is still shown in failure reports. Thanks Floris Bruynooghe. -- fix issue437 where assertion rewriting could cause pytest-xdist slaves +- fix issue437 where assertion rewriting could cause pytest-xdist worker nodes to collect different tests. Thanks Bruno Oliveira. - fix issue555: add "errors" attribute to capture-streams to satisfy diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.2.rst index f6ce178a107..9c3b7d96b07 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.2.rst @@ -1,7 +1,7 @@ pytest-2.6.2: few fixes and cx_freeze support =========================================================================== -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is drop-in compatible to 2.5.2 and 2.6.X. It also brings support for including pytest with cx_freeze or similar diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst index 7353dfee71c..56973a2b2f7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst @@ -1,7 +1,7 @@ pytest-2.6.3: fixes and little improvements =========================================================================== -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is drop-in compatible to 2.5.2 and 2.6.X. See below for the changes and see docs at: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst index d63081edb1b..2840178a07f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst @@ -1,7 +1,7 @@ pytest-2.7.0: fixes, features, speed improvements =========================================================================== -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.6.X. @@ -35,7 +35,7 @@ holger krekel - fix issue435: make reload() work when assert rewriting is active. Thanks Daniel Hahler. -- fix issue616: conftest.py files and their contained fixutres are now +- fix issue616: conftest.py files and their contained fixtures are now properly considered for visibility, independently from the exact current working directory and test arguments that are used. Many thanks to Eric Siegerman and his PR235 which contains @@ -52,7 +52,7 @@ holger krekel - add ability to set command line options by environment variable PYTEST_ADDOPTS. - added documentation on the new pytest-dev teams on bitbucket and - github. See https://pytest.org/latest/contributing.html . + github. See https://pytest.org/en/stable/contributing.html . Thanks to Anatoly for pushing and initial work on this. - fix issue650: new option ``--docttest-ignore-import-errors`` which diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst index fdc71eebba9..5110c085e01 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst @@ -1,7 +1,7 @@ pytest-2.7.1: bug fixes ======================= -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.7.0. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.2.rst index 1e3950de4d0..93e5b64eeed 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.7.2.rst @@ -1,7 +1,7 @@ pytest-2.7.2: bug fixes ======================= -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.7.1. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.2.rst index d7028616142..e4726338852 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.2.rst @@ -1,7 +1,7 @@ pytest-2.8.2: bug fixes ======================= -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.8.1. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.3.rst index b131a7e1f14..3f357252bb6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.3.rst @@ -1,7 +1,7 @@ pytest-2.8.3: bug fixes ======================= -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.8.2. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.4.rst index a09629cef09..adbdecc87ea 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.4.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.4.rst @@ -1,7 +1,7 @@ pytest-2.8.4 ============ -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.8.2. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.5.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.5.rst index 7409022a137..c5343d1ea72 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.5.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.5.rst @@ -1,7 +1,7 @@ pytest-2.8.5 ============ -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.8.4. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.6.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.6.rst index 215fae51eac..5d6565b16a3 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.6.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.6.rst @@ -1,7 +1,7 @@ pytest-2.8.6 ============ -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.8.5. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.7.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.7.rst index 9005f56363a..8236a096669 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.7.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.8.7.rst @@ -4,7 +4,7 @@ pytest-2.8.7 This is a hotfix release to solve a regression in the builtin monkeypatch plugin that got introduced in 2.8.6. -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. This release is supposed to be drop-in compatible to 2.8.5. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst index c079fdf6bfd..8c2ee05f9bf 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst @@ -1,7 +1,7 @@ pytest-2.9.0 ============ -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. See below for the changes and see docs at: @@ -75,7 +75,7 @@ The py.test Development Team **Changes** -* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been +* **Important**: `py.code <https://pylib.readthedocs.io/en/stable/code.html>`_ has been merged into the ``pytest`` repository as ``pytest._code``. This decision was made because ``py.code`` had very few uses outside ``pytest`` and the fact that it was in a different repository made it difficult to fix bugs on @@ -88,7 +88,7 @@ The py.test Development Team **experimental**, so you definitely should not import it explicitly! Please note that the original ``py.code`` is still available in - `pylib <https://pylib.readthedocs.io>`_. + `pylib <https://pylib.readthedocs.io/en/stable/>`_. * ``pytest_enter_pdb`` now optionally receives the pytest config object. Thanks `@nicoddemus`_ for the PR. @@ -131,7 +131,7 @@ The py.test Development Team with same name. -.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing +.. _`traceback style docs`: https://pytest.org/en/stable/usage.html#modifying-python-traceback-printing .. _#1422: https://github.com/pytest-dev/pytest/issues/1422 .. _#1379: https://github.com/pytest-dev/pytest/issues/1379 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst index c71f3851638..47bc2e6d38b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst @@ -1,7 +1,7 @@ pytest-2.9.1 ============ -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. See below for the changes and see docs at: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst index 8f274cdf398..ffd8dc58ed5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst @@ -1,7 +1,7 @@ pytest-2.9.2 ============ -pytest is a mature Python testing tool with more than a 1100 tests +pytest is a mature Python testing tool with more than 1100 tests against itself, passing on many different interpreters and platforms. See below for the changes and see docs at: @@ -66,8 +66,8 @@ The py.test Development Team .. _#510: https://github.com/pytest-dev/pytest/issues/510 .. _#1506: https://github.com/pytest-dev/pytest/pull/1506 -.. _#1496: https://github.com/pytest-dev/pytest/issue/1496 -.. _#1524: https://github.com/pytest-dev/pytest/issue/1524 +.. _#1496: https://github.com/pytest-dev/pytest/issues/1496 +.. _#1524: https://github.com/pytest-dev/pytest/pull/1524 .. _@astraw38: https://github.com/astraw38 .. _@hackebrot: https://github.com/hackebrot diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst index ca3e9e32763..5de38911482 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst @@ -3,7 +3,7 @@ pytest-3.0.0 The pytest team is proud to announce the 3.0.0 release! -pytest is a mature Python testing tool with more than a 1600 tests +pytest is a mature Python testing tool with more than 1600 tests against itself, passing on many different interpreters and platforms. This release contains a lot of bugs fixes and improvements, and much of diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst index eb6f6a50ef7..8f5cfe411aa 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst @@ -8,7 +8,7 @@ drop-in replacement. To upgrade: pip install --upgrade pytest -The changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst index 4af412fc5ee..86ba82ca6e6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst @@ -8,7 +8,7 @@ drop-in replacement. To upgrade:: pip install --upgrade pytest -The changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst index 896d4787304..89a2e0c744e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst @@ -8,7 +8,7 @@ being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst index 855bc56d5b8..72c2d29464d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst @@ -8,7 +8,7 @@ being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst index 2f369827588..97edb7d4628 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst index 149c2d65e1a..9c072cedcca 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst index b37e4f61dee..4b7e075e76a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst index 99cc6bdbe20..55277067948 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst @@ -3,13 +3,13 @@ pytest-3.1.0 The pytest team is proud to announce the 3.1.0 release! -pytest is a mature Python testing tool with more than a 1600 tests +pytest is a mature Python testing tool with more than 1600 tests against itself, passing on many different interpreters and platforms. This release contains a bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: -http://doc.pytest.org/en/latest/changelog.html +http://doc.pytest.org/en/stable/changelog.html For complete documentation, please visit: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst index 4ce7531977c..135b2fe8443 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst index 8ed0c93e9ad..a9b85c4715c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst index d7771f92232..bc2b85fcfd5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.0.rst index b53df270219..ff3c000b0e7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.0.rst @@ -3,17 +3,17 @@ pytest-3.10.0 The pytest team is proud to announce the 3.10.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.1.rst index 556b24ae15b..ad365f63474 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.10.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst index 4d2830edd2d..edc66a28e78 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst @@ -3,13 +3,13 @@ pytest-3.2.0 The pytest team is proud to announce the 3.2.0 release! -pytest is a mature Python testing tool with more than a 1600 tests +pytest is a mature Python testing tool with more than 1600 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - http://doc.pytest.org/en/latest/changelog.html + http://doc.pytest.org/en/stable/changelog.html For complete documentation, please visit: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst index afe2c5bfe2c..c40217d311d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst index 88e32873a1b..5e6c43ab177 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst index ddfda4d132f..50dce29c1ad 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst index 65e486b7aa2..ff0b35781b1 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst index 2e5304c6f27..68caccbdbc5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst index e0740e7d592..1cbf2c448c8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst @@ -3,13 +3,13 @@ pytest-3.3.0 The pytest team is proud to announce the 3.3.0 release! -pytest is a mature Python testing tool with more than a 1600 tests +pytest is a mature Python testing tool with more than 1600 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - http://doc.pytest.org/en/latest/changelog.html + http://doc.pytest.org/en/stable/changelog.html For complete documentation, please visit: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.1.rst index 7eed836ae6d..98b6fa6c1ba 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.2.rst index d9acef947dd..7a2577d1ff8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.3.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.0.rst index df1e004f1cc..6ab5b124a25 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.0.rst @@ -3,13 +3,13 @@ pytest-3.4.0 The pytest team is proud to announce the 3.4.0 release! -pytest is a mature Python testing tool with more than a 1600 tests +pytest is a mature Python testing tool with more than 1600 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - http://doc.pytest.org/en/latest/changelog.html + http://doc.pytest.org/en/stable/changelog.html For complete documentation, please visit: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.1.rst index e37f5d7e240..d83949453a2 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.2.rst index 8e9988228fa..07cd9d3a8ba 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.4.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.0.rst index 54a05cea24d..6bc2f3cd0cb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.0.rst @@ -3,13 +3,13 @@ pytest-3.5.0 The pytest team is proud to announce the 3.5.0 release! -pytest is a mature Python testing tool with more than a 1600 tests +pytest is a mature Python testing tool with more than 1600 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - http://doc.pytest.org/en/latest/changelog.html + http://doc.pytest.org/en/stable/changelog.html For complete documentation, please visit: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.1.rst index 91f14390eeb..802be036848 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.5.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.0.rst index 37361cf4add..44b178c169f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.0.rst @@ -3,13 +3,13 @@ pytest-3.6.0 The pytest team is proud to announce the 3.6.0 release! -pytest is a mature Python testing tool with more than a 1600 tests +pytest is a mature Python testing tool with more than 1600 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - http://doc.pytest.org/en/latest/changelog.html + http://doc.pytest.org/en/stable/changelog.html For complete documentation, please visit: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.1.rst index 3bedcf46a85..d971a3d4907 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.2.rst index a1215f57689..9d919957939 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.3.rst index 07bb05a3d72..4dda2460dac 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.3.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.4.rst index fd6cff50305..2c0f9efeccf 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.4.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.6.4.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.0.rst index 922b22517e9..89908a9101c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.0.rst @@ -3,13 +3,13 @@ pytest-3.7.0 The pytest team is proud to announce the 3.7.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - http://doc.pytest.org/en/latest/changelog.html + http://doc.pytest.org/en/stable/changelog.html For complete documentation, please visit: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.1.rst index e1c35123dbf..7da5a3e1f7d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.2.rst index 4f7e0744d50..fcc6121752d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.3.rst index 454d4fdfee7..ee87da60d23 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.3.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. +The full changelog is available at http://doc.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.4.rst index 0ab8938f4f6..45be4293885 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.4.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.7.4.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.0.rst index 1fc344ea23e..8c35a44f6d5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.0.rst @@ -3,17 +3,17 @@ pytest-3.8.0 The pytest team is proud to announce the 3.8.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.1.rst index 3e05e58cb3f..f8f8accc4c9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.2.rst index 124c33aa40e..9ea94c98a21 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.8.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: @@ -20,7 +20,7 @@ Thanks to all who contributed to this release, among them: * Jeffrey Rackauckas * Jose Carlos Menezes * Ronny Pfannschmidt -* Zac-HD +* Zac Hatfield-Dodds * iwanb diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.0.rst index 14cfbe9037d..0be6cf5be8a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.0.rst @@ -3,17 +3,17 @@ pytest-3.9.0 The pytest team is proud to announce the 3.9.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.1.rst index f050e465305..e1afb3759d2 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.2.rst index 1440831cb93..63e94e5aabb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.3.rst index 8d84b4cabcb..661ddb5cb54 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-3.9.3.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.0.rst index e5ad69b5fd6..5eb0107758a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.0.rst @@ -3,17 +3,17 @@ pytest-4.0.0 The pytest team is proud to announce the 4.0.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.1.rst index 31b222c03b5..2902a6db9fb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.2.rst index 3b6e4be7183..f439b88fe2c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.0.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.0.rst index b7a076f61c9..314564eeb6f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.0.rst @@ -3,17 +3,17 @@ pytest-4.1.0 The pytest team is proud to announce the 4.1.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.1.rst index 80644fc84ef..1f45e082f89 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.1.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.0.rst index 6c262c1e01b..bcd7f775479 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.0.rst @@ -3,17 +3,17 @@ pytest-4.2.0 The pytest team is proud to announce the 4.2.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.1.rst index 5aec022df0b..36beafe11d2 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.2.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.0.rst index 59393814846..3b0b4280922 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.0.rst @@ -3,17 +3,17 @@ pytest-4.3.0 The pytest team is proud to announce the 4.3.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.1.rst index 45d14fffed9..4251c744e55 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.3.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: @@ -21,7 +21,6 @@ Thanks to all who contributed to this release, among them: * Kyle Altendorf * Stephan Hoyer * Zac Hatfield-Dodds -* Zac-HD * songbowen diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.0.rst index 4c5bcbc7d35..dc89739d0aa 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.0.rst @@ -3,17 +3,17 @@ pytest-4.4.0 The pytest team is proud to announce the 4.4.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.1.rst index 12c0ee7798b..1272cd8fde1 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.2.rst index 4fe2dac56b3..5876e83b3b6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.4.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.5.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.5.0.rst index 084579ac419..d2a05d4f795 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.5.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.5.0.rst @@ -3,17 +3,17 @@ pytest-4.5.0 The pytest team is proud to announce the 4.5.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: @@ -28,7 +28,6 @@ Thanks to all who contributed to this release, among them: * Pulkit Goyal * Samuel Searles-Bryant * Zac Hatfield-Dodds -* Zac-HD Happy testing, diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.0.rst index 373f5d66eb7..a82fdd47d6f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.0.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.0.rst @@ -3,17 +3,17 @@ pytest-4.6.0 The pytest team is proud to announce the 4.6.0 release! -pytest is a mature Python testing tool with more than a 2000 tests +pytest is a mature Python testing tool with more than 2000 tests against itself, passing on many different interpreters and platforms. This release contains a number of bugs fixes and improvements, so users are encouraged to take a look at the CHANGELOG: - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ As usual, you can upgrade from pypi via: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.1.rst index 78d017544d2..c79839b7b52 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.1.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.1.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.10.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.10.rst deleted file mode 100644 index 57938b8751a..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.10.rst +++ /dev/null @@ -1,20 +0,0 @@ -pytest-4.6.10 -======================================= - -pytest 4.6.10 has just been released to PyPI. - -This is a bug-fix release, being a drop-in replacement. To upgrade:: - - pip install --upgrade pytest - -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. - -Thanks to all who contributed to this release, among them: - -* Anthony Sottile -* Bruno Oliveira -* Fernando Mez - - -Happy testing, -The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.11.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.11.rst deleted file mode 100644 index 276584bdf52..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.11.rst +++ /dev/null @@ -1,20 +0,0 @@ -pytest-4.6.11 -======================================= - -pytest 4.6.11 has just been released to PyPI. - -This is a bug-fix release, being a drop-in replacement. To upgrade:: - - pip install --upgrade pytest - -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. - -Thanks to all who contributed to this release, among them: - -* Anthony Sottile -* Bruno Oliveira -* Sviatoslav Sydorenko - - -Happy testing, -The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.2.rst index 8526579b9e7..cfc595293ae 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.2.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.2.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.3.rst index 0bfb355a15a..f578464a7a3 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.3.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.3.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.4.rst index 7b35ed4f0d4..0eefcbeb1c2 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.4.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.4.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.5.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.5.rst index 6998d4e4c5f..1ebf361fdf9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.5.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.5.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.6.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.6.rst index c47a31695b2..b3bf1e431c7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.6.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.6.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.7.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.7.rst index 0e6cf6a950a..f9d01845ec2 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.7.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.7.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.8.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.8.rst index 3c04e5dbe9b..5cabe7826e9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.8.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.8.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.9.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.9.rst index ae0478c52d9..7f7bb5996ea 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.9.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-4.6.9.rst @@ -7,7 +7,7 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. Thanks to all who contributed to this release, among them: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.0.rst new file mode 100644 index 00000000000..f5e593e9d88 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.0.rst @@ -0,0 +1,46 @@ +pytest-5.0.0 +======================================= + +The pytest team is proud to announce the 5.0.0 release! + +pytest is a mature Python testing tool with more than 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Dirk Thomas +* Evan Kepner +* Florian Bruhin +* Hugo +* Kevin J. Foley +* Pulkit Goyal +* Ralph Giles +* Ronny Pfannschmidt +* Thomas Grainger +* Thomas Hisch +* Tim Gates +* Victor Maryama +* Yuri Apollov +* Zac Hatfield-Dodds +* curiousjazz77 +* patriksevallius + + +Happy testing, +The Pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.1.rst new file mode 100644 index 00000000000..e16a8f716f1 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.0.1.rst @@ -0,0 +1,25 @@ +pytest-5.0.1 +======================================= + +pytest 5.0.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* AmirElkess +* Andreu Vallbona Plazas +* Anthony Sottile +* Bruno Oliveira +* Florian Bruhin +* Michael Moore +* Niklas Meinzer +* Thomas Grainger + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.0.rst new file mode 100644 index 00000000000..9ab54ff9730 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.0.rst @@ -0,0 +1,56 @@ +pytest-5.1.0 +======================================= + +The pytest team is proud to announce the 5.1.0 release! + +pytest is a mature Python testing tool with more than 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Albert Tugushev +* Alexey Zankevich +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* David Röthlisberger +* Florian Bruhin +* Ilya Stepin +* Jon Dufresne +* Kaiqi +* Max R +* Miro Hrončok +* Oliver Bestwalter +* Ran Benita +* Ronny Pfannschmidt +* Samuel Searles-Bryant +* Semen Zhydenko +* Steffen Schroeder +* Thomas Grainger +* Tim Hoffmann +* William Woodall +* Wojtek Erbetowski +* Xixi Zhao +* Yash Todi +* boris +* dmitry.dygalo +* helloocc +* martbln +* mei-li + + +Happy testing, +The Pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.1.rst new file mode 100644 index 00000000000..bb8de48014a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.1.rst @@ -0,0 +1,24 @@ +pytest-5.1.1 +======================================= + +pytest 5.1.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Florian Bruhin +* Hugo van Kemenade +* Ran Benita +* Ronny Pfannschmidt + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.2.rst new file mode 100644 index 00000000000..c4cb8e3fb44 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.2.rst @@ -0,0 +1,23 @@ +pytest-5.1.2 +======================================= + +pytest 5.1.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Andrzej Klajnert +* Anthony Sottile +* Bruno Oliveira +* Christian Neumüller +* Robert Holt +* linchiwei123 + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.3.rst new file mode 100644 index 00000000000..c4e88aed28e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.1.3.rst @@ -0,0 +1,23 @@ +pytest-5.1.3 +======================================= + +pytest 5.1.3 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Christian Neumüller +* Daniel Hahler +* Gene Wood +* Hugo + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.0.rst new file mode 100644 index 00000000000..f43767b7506 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.0.rst @@ -0,0 +1,35 @@ +pytest-5.2.0 +======================================= + +The pytest team is proud to announce the 5.2.0 release! + +pytest is a mature Python testing tool with more than 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Andrzej Klajnert +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* James Cooke +* Michael Goerz +* Ran Benita +* Tomáš Chvátal + + +Happy testing, +The Pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.1.rst new file mode 100644 index 00000000000..fe42b9bf15f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.1.rst @@ -0,0 +1,23 @@ +pytest-5.2.1 +======================================= + +pytest 5.2.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Florian Bruhin +* Hynek Schlawack +* Kevin J. Foley +* tadashigaki + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.2.rst new file mode 100644 index 00000000000..89fd6a534d4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.2.rst @@ -0,0 +1,29 @@ +pytest-5.2.2 +======================================= + +pytest 5.2.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Albert Tugushev +* Andrzej Klajnert +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Florian Bruhin +* Nattaphoom Chaipreecha +* Oliver Bestwalter +* Philipp Loose +* Ran Benita +* Victor Maryama +* Yoav Caspi + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.3.rst new file mode 100644 index 00000000000..bab174495d9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.3.rst @@ -0,0 +1,28 @@ +pytest-5.2.3 +======================================= + +pytest 5.2.3 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Brett Cannon +* Bruno Oliveira +* Daniel Hahler +* Daniil Galiev +* David Szotten +* Florian Bruhin +* Patrick Harmon +* Ran Benita +* Zac Hatfield-Dodds +* Zak Hassan + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.4.rst new file mode 100644 index 00000000000..5f518967975 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.2.4.rst @@ -0,0 +1,22 @@ +pytest-5.2.4 +======================================= + +pytest 5.2.4 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Hugo +* Michael Shields + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.0.rst new file mode 100644 index 00000000000..e13a71f09aa --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.0.rst @@ -0,0 +1,45 @@ +pytest-5.3.0 +======================================= + +The pytest team is proud to announce the 5.3.0 release! + +pytest is a mature Python testing tool with more than 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bugs fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* AnjoMan +* Anthony Sottile +* Anton Lodder +* Bruno Oliveira +* Daniel Hahler +* Gregory Lee +* Josh Karpel +* JoshKarpel +* Joshua Storck +* Kale Kundert +* MarcoGorelli +* Michael Krebs +* NNRepos +* Ran Benita +* TH3CHARLie +* Tibor Arpas +* Zac Hatfield-Dodds +* 林玮 + + +Happy testing, +The Pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.1.rst new file mode 100644 index 00000000000..d575bb70e3f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.1.rst @@ -0,0 +1,26 @@ +pytest-5.3.1 +======================================= + +pytest 5.3.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Felix Yan +* Florian Bruhin +* Mark Dickinson +* Nikolay Kondratyev +* Steffen Schroeder +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.2.rst new file mode 100644 index 00000000000..d562a33fb0f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.2.rst @@ -0,0 +1,26 @@ +pytest-5.3.2 +======================================= + +pytest 5.3.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Claudio Madotto +* Daniel Hahler +* Jared Vasquez +* Michael Rose +* Ran Benita +* Ronny Pfannschmidt +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.3.rst new file mode 100644 index 00000000000..40a6fb5b560 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.3.rst @@ -0,0 +1,30 @@ +pytest-5.3.3 +======================================= + +pytest 5.3.3 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Adam Johnson +* Alexandre Mulatinho +* Anthony Sottile +* Bruno Oliveira +* Chris NeJame +* Daniel Hahler +* Hugo van Kemenade +* Marcelo Duarte Trevisani +* PaulC +* Ran Benita +* Ryan Barner +* Seth Junot +* marc + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.4.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.4.rst new file mode 100644 index 00000000000..0750a9d404e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.4.rst @@ -0,0 +1,20 @@ +pytest-5.3.4 +======================================= + +pytest 5.3.4 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira +* Daniel Hahler +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.5.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.5.rst new file mode 100644 index 00000000000..e632ce85388 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.3.5.rst @@ -0,0 +1,19 @@ +pytest-5.3.5 +======================================= + +pytest 5.3.5 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Daniel Hahler +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.0.rst new file mode 100644 index 00000000000..43dffc9290e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.0.rst @@ -0,0 +1,59 @@ +pytest-5.4.0 +======================================= + +The pytest team is proud to announce the 5.4.0 release! + +pytest is a mature Python testing tool with more than 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bug fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Christoph Buelter +* Christoph Bülter +* Daniel Arndt +* Daniel Hahler +* Holger Kohr +* Hugo +* Hugo van Kemenade +* Jakub Mitoraj +* Kyle Altendorf +* Minuddin Ahmed Rana +* Nathaniel Compton +* ParetoLife +* Pauli Virtanen +* Philipp Loose +* Ran Benita +* Ronny Pfannschmidt +* Stefan Scherfke +* Stefano Mazzucco +* TWood67 +* Tobias Schmidt +* Tomáš Gavenčiak +* Vinay Calastry +* Vladyslav Rachek +* Zac Hatfield-Dodds +* captainCapitalism +* cmachalo +* gftea +* kpinc +* rebecca-palmer +* sdementen + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.1.rst new file mode 100644 index 00000000000..f6a64efa492 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.1.rst @@ -0,0 +1,18 @@ +pytest-5.4.1 +======================================= + +pytest 5.4.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.2.rst new file mode 100644 index 00000000000..d742dd4aad4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.2.rst @@ -0,0 +1,22 @@ +pytest-5.4.2 +======================================= + +pytest 5.4.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Hahler +* Ran Benita +* Ronny Pfannschmidt + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.3.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.3.rst new file mode 100644 index 00000000000..6c995c16339 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-5.4.3.rst @@ -0,0 +1,21 @@ +pytest-5.4.3 +======================================= + +pytest 5.4.3 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Bruno Oliveira +* Ran Benita +* Tor Colvin + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0.rst new file mode 100644 index 00000000000..9706fe59bc7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0.rst @@ -0,0 +1,40 @@ +pytest-6.0.0 +======================================= + +The pytest team is proud to announce the 6.0.0 release! + +pytest is a mature Python testing tool with more than 2000 tests +against itself, passing on many different interpreters and platforms. + +This release contains a number of bug fixes and improvements, so users are encouraged +to take a look at the CHANGELOG: + + https://docs.pytest.org/en/latest/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/latest/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Arvin Firouzi +* Bruno Oliveira +* Debi Mishra +* Garrett Thomas +* Hugo van Kemenade +* Kelton Bassingthwaite +* Kostis Anagnostopoulos +* Lewis Cowles +* Miro Hrončok +* Ran Benita +* Simon K +* Zac Hatfield-Dodds + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0rc1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0rc1.rst new file mode 100644 index 00000000000..5690b514baf --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.0rc1.rst @@ -0,0 +1,67 @@ +pytest-6.0.0rc1 +======================================= + +pytest 6.0.0rc1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Alfredo Deza +* Andreas Maier +* Andrew +* Anthony Sottile +* ArtyomKaltovich +* Bruno Oliveira +* Claire Cecil +* Curt J. Sampson +* Daniel +* Daniel Hahler +* Danny Sepler +* David Diaz Barquero +* Fabio Zadrozny +* Felix Nieuwenhuizen +* Florian Bruhin +* Florian Dahlitz +* Gleb Nikonorov +* Hugo van Kemenade +* Hunter Richards +* Katarzyna Król +* Katrin Leinweber +* Keri Volans +* Lewis Belcher +* Lukas Geiger +* Martin Michlmayr +* Mattwmaster58 +* Maximilian Cosmo Sitter +* Nikolay Kondratyev +* Pavel Karateev +* Paweł Wilczyński +* Prashant Anand +* Ram Rachum +* Ran Benita +* Ronny Pfannschmidt +* Ruaridh Williamson +* Simon K +* Tim Hoffmann +* Tor Colvin +* Vlad-Radz +* Xinbin Huang +* Zac Hatfield-Dodds +* earonesty +* gaurav dhameeja +* gdhameeja +* ibriquem +* mcsitter +* piotrhm +* smarie +* symonk +* xuiqzy + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.1.rst new file mode 100644 index 00000000000..33fdbed3f61 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.1.rst @@ -0,0 +1,21 @@ +pytest-6.0.1 +======================================= + +pytest 6.0.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira +* Mattreex +* Ran Benita +* hp310780 + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.2.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.2.rst new file mode 100644 index 00000000000..16eabc5863d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.0.2.rst @@ -0,0 +1,19 @@ +pytest-6.0.2 +======================================= + +pytest 6.0.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.0.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.0.rst new file mode 100644 index 00000000000..f4b571ae846 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.0.rst @@ -0,0 +1,44 @@ +pytest-6.1.0 +======================================= + +The pytest team is proud to announce the 6.1.0 release! + +This release contains new features, improvements, bug fixes, and breaking changes, so users +are encouraged to take a look at the CHANGELOG carefully: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* C. Titus Brown +* Drew Devereux +* Faris A Chugthai +* Florian Bruhin +* Hugo van Kemenade +* Hynek Schlawack +* Joseph Lucas +* Kamran Ahmad +* Mattreex +* Maximilian Cosmo Sitter +* Ran Benita +* Rüdiger Busche +* Sam Estep +* Sorin Sbarnea +* Thomas Grainger +* Vipul Kumar +* Yutaro Ikeda +* hp310780 + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.1.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.1.rst new file mode 100644 index 00000000000..e09408fdeea --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/announce/release-6.1.1.rst @@ -0,0 +1,18 @@ +pytest-6.1.1 +======================================= + +pytest 6.1.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/assert.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/assert.rst index 1e06d9757b3..7e43b07fd75 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/assert.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/assert.rst @@ -31,7 +31,7 @@ you will see the return value of the function call: $ pytest test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -47,7 +47,9 @@ you will see the return value of the function call: E + where 3 = f() test_assert1.py:6: AssertionError - ========================= 1 failed in 0.12 seconds ========================= + ========================= short test summary info ========================== + FAILED test_assert1.py::test_function - assert 3 == 4 + ============================ 1 failed in 0.12s ============================= ``pytest`` has support for showing the values of the most common subexpressions including calls, attributes, comparisons, and binary and unary @@ -96,7 +98,7 @@ and if you need to have access to the actual exception info you may use: f() assert "maximum recursion" in str(excinfo.value) -``excinfo`` is a ``ExceptionInfo`` instance, which is a wrapper around +``excinfo`` is an ``ExceptionInfo`` instance, which is a wrapper around the actual exception raised. The main attributes of interest are ``.type``, ``.value`` and ``.traceback``. @@ -186,7 +188,7 @@ if you run this module: $ pytest test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -208,7 +210,9 @@ if you run this module: E Use -v to get the full diff test_assert2.py:6: AssertionError - ========================= 1 failed in 0.12 seconds ========================= + ========================= short test summary info ========================== + FAILED test_assert2.py::test_set_comparison - AssertionError: assert {'0'... + ============================ 1 failed in 0.12s ============================= Special comparisons are done for a number of cases: @@ -238,14 +242,17 @@ file which provides an alternative explanation for ``Foo`` objects: def pytest_assertrepr_compare(op, left, right): if isinstance(left, Foo) and isinstance(right, Foo) and op == "==": - return ["Comparing Foo instances:", " vals: %s != %s" % (left.val, right.val)] + return [ + "Comparing Foo instances:", + " vals: {} != {}".format(left.val, right.val), + ] now, given this test module: .. code-block:: python # content of test_foocompare.py - class Foo(object): + class Foo: def __init__(self, val): self.val = val @@ -276,7 +283,9 @@ the conftest file: E vals: 1 != 2 test_foocompare.py:12: AssertionError - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_foocompare.py::test_compare - assert Comparing Foo instances: + 1 failed in 0.12s .. _assert-details: .. _`assert introspection`: @@ -285,8 +294,6 @@ Assertion introspection details ------------------------------- - - Reporting details about a failing assertion is achieved by rewriting assert statements before they are run. Rewritten assert statements put introspection information into the assertion failure message. ``pytest`` only rewrites test @@ -294,7 +301,7 @@ modules directly discovered by its test collection process, so **asserts in supporting modules which are not themselves test modules will not be rewritten**. You can manually enable assertion rewriting for an imported module by calling -`register_assert_rewrite <https://docs.pytest.org/en/latest/writing_plugins.html#assertion-rewriting>`_ +`register_assert_rewrite <https://docs.pytest.org/en/stable/writing_plugins.html#assertion-rewriting>`_ before you import it (a good place to do that is in your root ``conftest.py``). For further information, Benjamin Peterson wrote up `Behind the scenes of pytest's new assertion rewriting <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_. @@ -333,15 +340,3 @@ If this is the case you have two options: ``PYTEST_DONT_REWRITE`` to its docstring. * Disable rewriting for all modules by using ``--assert=plain``. - - - - Add assert rewriting as an alternate introspection technique. - - - Introduce the ``--assert`` option. Deprecate ``--no-assert`` and - ``--nomagic``. - - - Removes the ``--no-assert`` and ``--nomagic`` options. - Removes the ``--assert=reinterp`` option. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst index 56afd98afa7..d5b2d79d633 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst @@ -3,6 +3,61 @@ Backwards Compatibility Policy ============================== +.. versionadded: 6.0 + +pytest is actively evolving and is a project that has been decades in the making, +we keep learning about new and better structures to express different details about testing. + +While we implement those modifications we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors. + +As of now, pytest considers multipe types of backward compatibility transitions: + +a) trivial: APIs which trivially translate to the new mechanism, + and do not cause problematic changes. + + We try to support those indefinitely while encouraging users to switch to newer/better mechanisms through documentation. + +b) transitional: the old and new API don't conflict + and we can help users transition by using warnings, while supporting both for a prolonged time. + + We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0). + + When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed. + + +c) true breakage: should only to be considered when normal transition is unreasonably unsustainable and would offset important development/features by years. + In addition, they should be limited to APIs where the number of actual users is very small (for example only impacting some plugins), and can be coordinated with the community in advance. + + Examples for such upcoming changes: + + * removal of ``pytest_runtest_protocol/nextitem`` - `#895`_ + * rearranging of the node tree to include ``FunctionDefinition`` + * rearranging of ``SetupState`` `#895`_ + + True breakages must be announced first in an issue containing: + + * Detailed description of the change + * Rationale + * Expected impact on users and plugin authors (example in `#895`_) + + After there's no hard *-1* on the issue it should be followed up by an initial proof-of-concept Pull Request. + + This POC serves as both a coordination point to assess impact and potential inspriation to come up with a transitional solution after all. + + After a reasonable amount of time the PR can be merged to base a new major release. + + For the PR to mature from POC to acceptance, it must contain: + * Setup of deprecation errors/warnings that help users fix and port their code. If it is possible to introduce a deprecation period under the current series, before the true breakage, it should be introduced in a separate PR and be part of the current release stream. + * Detailed description of the rationale and examples on how to port code in ``doc/en/deprecations.rst``. + + +History +========= + + +Focus primary on smooth transition - stance (pre 6.0) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary. With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around. @@ -20,3 +75,6 @@ Deprecation Roadmap Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`. We track future deprecation and removal of features using milestones and the `deprecation <https://github.com/pytest-dev/pytest/issues?q=label%3A%22type%3A+deprecation%22>`_ and `removal <https://github.com/pytest-dev/pytest/labels/type%3A%20removal>`_ labels on GitHub. + + +.. _`#895`: https://github.com/pytest-dev/pytest/issues/895 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst index a4c20695c3b..0fd58164c76 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/builtin.rst @@ -23,7 +23,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a cache.get(key, default) cache.set(key, value) - Keys must be a ``/`` separated value, where the first part is usually the + Keys must be ``/`` separated strings, where the first part is usually the name of your plugin or application to avoid clashes with other cache users. Values can be any object handled by the json stdlib module. @@ -57,7 +57,8 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a ``out`` and ``err`` will be ``byte`` objects. doctest_namespace [session scope] - Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. + Fixture that returns a :py:class:`dict` that will be injected into the + namespace of doctests. pytestconfig [session scope] Session-scoped fixture that returns the :class:`_pytest.config.Config` object. @@ -69,11 +70,13 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a ... record_property - Add an extra properties the calling test. + Add extra properties to the calling test. + User properties become part of the test report and are available to the configured reporters, like JUnit XML. - The fixture is callable with ``(name, value)``, with value being automatically - xml-encoded. + + The fixture is callable with ``name, value``. The value is automatically + XML-encoded. Example:: @@ -82,12 +85,15 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a record_xml_attribute Add extra xml attributes to the tag for the calling test. - The fixture is callable with ``(name, value)``, with value being - automatically xml-encoded + + The fixture is callable with ``name, value``. The value is + automatically XML-encoded. record_testsuite_property [session scope] - Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to - writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family. + Record a new ``<property>`` tag as child of the root ``<testsuite>``. + + This is suitable to writing global information regarding the entire test + suite, and is compatible with ``xunit2`` JUnit family. This is a ``session``-scoped fixture which is called with ``(name, value)``. Example: @@ -99,19 +105,28 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. + .. warning:: + + Currently this fixture **does not work** with the + `pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See issue + `#7767 <https://github.com/pytest-dev/pytest/issues/7767>`__ for details. + caplog Access and control log capturing. Captured logs are available through the following properties/methods:: + * caplog.messages -> list of format-interpolated log messages * caplog.text -> string containing formatted log output * caplog.records -> list of logging.LogRecord instances * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string monkeypatch - The returned ``monkeypatch`` fixture provides these - helper methods to modify objects, dictionaries or os.environ:: + A convenient fixture for monkey-patching. + + The fixture provides these methods to modify objects, dictionaries or + os.environ:: monkeypatch.setattr(obj, name, value, raising=True) monkeypatch.delattr(obj, name, raising=True) @@ -122,10 +137,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a monkeypatch.syspath_prepend(path) monkeypatch.chdir(path) - All modifications will be undone after the requesting - test function or fixture has finished. The ``raising`` - parameter determines if a KeyError or AttributeError - will be raised if the set/deletion operation has no target. + All modifications will be undone after the requesting test function or + fixture has finished. The ``raising`` parameter determines if a KeyError + or AttributeError will be raised if the set/deletion operation has no target. recwarn Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. @@ -140,29 +154,32 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session. tmpdir - Return a temporary directory path object - which is unique to each test function invocation, - created as a sub directory of the base temporary - directory. The returned object is a `py.path.local`_ - path object. + Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + The returned object is a `py.path.local`_ path object. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html tmp_path - Return a temporary directory path object - which is unique to each test function invocation, - created as a sub directory of the base temporary - directory. The returned object is a :class:`pathlib.Path` - object. + Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + The returned object is a :class:`pathlib.Path` object. .. note:: - in python < 3.6 this is a pathlib2.Path + In python < 3.6 this is a pathlib2.Path. - no tests ran in 0.12 seconds + no tests ran in 0.12s -You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like:: +You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like: + +.. code-block:: python import pytest + help(pytest) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/cache.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/cache.rst index 27a8449108c..42ca473545d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/cache.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/cache.rst @@ -33,15 +33,18 @@ Other plugins may access the `config.cache`_ object to set/get Rerunning only failures or failures first ----------------------------------------------- -First, let's create 50 test invocation of which only 2 fail:: +First, let's create 50 test invocation of which only 2 fail: + +.. code-block:: python # content of test_50.py import pytest + @pytest.mark.parametrize("i", range(50)) def test_num(i): if i in (17, 25): - pytest.fail("bad luck") + pytest.fail("bad luck") If you run this for the first time you will see two failures: @@ -57,10 +60,10 @@ If you run this for the first time you will see two failures: @pytest.mark.parametrize("i", range(50)) def test_num(i): if i in (17, 25): - > pytest.fail("bad luck") - E Failed: bad luck + > pytest.fail("bad luck") + E Failed: bad luck - test_50.py:6: Failed + test_50.py:7: Failed _______________________________ test_num[25] _______________________________ i = 25 @@ -68,11 +71,14 @@ If you run this for the first time you will see two failures: @pytest.mark.parametrize("i", range(50)) def test_num(i): if i in (17, 25): - > pytest.fail("bad luck") - E Failed: bad luck + > pytest.fail("bad luck") + E Failed: bad luck - test_50.py:6: Failed - 2 failed, 48 passed in 0.12 seconds + test_50.py:7: Failed + ========================= short test summary info ========================== + FAILED test_50.py::test_num[17] - Failed: bad luck + FAILED test_50.py::test_num[25] - Failed: bad luck + 2 failed, 48 passed in 0.12s If you then run it with ``--lf``: @@ -80,10 +86,10 @@ If you then run it with ``--lf``: $ pytest --lf =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collected 50 items / 48 deselected / 2 selected + collected 2 items run-last-failure: rerun previous 2 failures test_50.py FF [100%] @@ -96,10 +102,10 @@ If you then run it with ``--lf``: @pytest.mark.parametrize("i", range(50)) def test_num(i): if i in (17, 25): - > pytest.fail("bad luck") - E Failed: bad luck + > pytest.fail("bad luck") + E Failed: bad luck - test_50.py:6: Failed + test_50.py:7: Failed _______________________________ test_num[25] _______________________________ i = 25 @@ -107,14 +113,17 @@ If you then run it with ``--lf``: @pytest.mark.parametrize("i", range(50)) def test_num(i): if i in (17, 25): - > pytest.fail("bad luck") - E Failed: bad luck + > pytest.fail("bad luck") + E Failed: bad luck - test_50.py:6: Failed - ================= 2 failed, 48 deselected in 0.12 seconds ================== + test_50.py:7: Failed + ========================= short test summary info ========================== + FAILED test_50.py::test_num[17] - Failed: bad luck + FAILED test_50.py::test_num[25] - Failed: bad luck + ============================ 2 failed in 0.12s ============================= -You have run only the two failing test from the last run, while 48 tests have -not been run ("deselected"). +You have run only the two failing tests from the last run, while the 48 passing +tests have not been run ("deselected"). Now, if you run with the ``--ff`` option, all tests will be run but the first previous failures will be executed first (as can be seen from the series @@ -124,7 +133,7 @@ of ``FF`` and dots): $ pytest --ff =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 50 items @@ -140,10 +149,10 @@ of ``FF`` and dots): @pytest.mark.parametrize("i", range(50)) def test_num(i): if i in (17, 25): - > pytest.fail("bad luck") - E Failed: bad luck + > pytest.fail("bad luck") + E Failed: bad luck - test_50.py:6: Failed + test_50.py:7: Failed _______________________________ test_num[25] _______________________________ i = 25 @@ -151,11 +160,14 @@ of ``FF`` and dots): @pytest.mark.parametrize("i", range(50)) def test_num(i): if i in (17, 25): - > pytest.fail("bad luck") - E Failed: bad luck + > pytest.fail("bad luck") + E Failed: bad luck - test_50.py:6: Failed - =================== 2 failed, 48 passed in 0.12 seconds ==================== + test_50.py:7: Failed + ========================= short test summary info ========================== + FAILED test_50.py::test_num[17] - Failed: bad luck + FAILED test_50.py::test_num[25] - Failed: bad luck + ======================= 2 failed, 48 passed in 0.12s ======================= .. _`config.cache`: @@ -182,16 +194,20 @@ The new config.cache object Plugins or conftest.py support code can get a cached value using the pytest ``config`` object. Here is a basic example plugin which -implements a :ref:`fixture` which re-uses previously created state -across pytest invocations:: +implements a :ref:`fixture <fixture>` which re-uses previously created state +across pytest invocations: + +.. code-block:: python # content of test_caching.py import pytest import time + def expensive_computation(): print("running expensive computation...") + @pytest.fixture def mydata(request): val = request.config.cache.get("example/value", None) @@ -201,6 +217,7 @@ across pytest invocations:: request.config.cache.set("example/value", val) return val + def test_function(mydata): assert mydata == 23 @@ -219,12 +236,14 @@ If you run this command for the first time, you can see the print statement: > assert mydata == 23 E assert 42 == 23 - test_caching.py:17: AssertionError + test_caching.py:20: AssertionError -------------------------- Captured stdout setup --------------------------- running expensive computation... - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_caching.py::test_function - assert 42 == 23 + 1 failed in 0.12s -If you run it a second time the value will be retrieved from +If you run it a second time, the value will be retrieved from the cache and nothing will be printed: .. code-block:: pytest @@ -240,10 +259,12 @@ the cache and nothing will be printed: > assert mydata == 23 E assert 42 == 23 - test_caching.py:17: AssertionError - 1 failed in 0.12 seconds + test_caching.py:20: AssertionError + ========================= short test summary info ========================== + FAILED test_caching.py::test_function - assert 42 == 23 + 1 failed in 0.12s -See the :ref:`cache-api` for more details. +See the :fixture:`config.cache fixture <cache>` for more details. Inspecting Cache content @@ -256,7 +277,7 @@ You can always peek at the content of the cache using the $ pytest --cache-show =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR cachedir: $PYTHON_PREFIX/.pytest_cache @@ -269,13 +290,66 @@ You can always peek at the content of the cache using the 'test_caching.py::test_function': True, 'test_foocompare.py::test_compare': True} cache/nodeids contains: - ['test_caching.py::test_function'] + ['test_50.py::test_num[0]', + 'test_50.py::test_num[10]', + 'test_50.py::test_num[11]', + 'test_50.py::test_num[12]', + 'test_50.py::test_num[13]', + 'test_50.py::test_num[14]', + 'test_50.py::test_num[15]', + 'test_50.py::test_num[16]', + 'test_50.py::test_num[17]', + 'test_50.py::test_num[18]', + 'test_50.py::test_num[19]', + 'test_50.py::test_num[1]', + 'test_50.py::test_num[20]', + 'test_50.py::test_num[21]', + 'test_50.py::test_num[22]', + 'test_50.py::test_num[23]', + 'test_50.py::test_num[24]', + 'test_50.py::test_num[25]', + 'test_50.py::test_num[26]', + 'test_50.py::test_num[27]', + 'test_50.py::test_num[28]', + 'test_50.py::test_num[29]', + 'test_50.py::test_num[2]', + 'test_50.py::test_num[30]', + 'test_50.py::test_num[31]', + 'test_50.py::test_num[32]', + 'test_50.py::test_num[33]', + 'test_50.py::test_num[34]', + 'test_50.py::test_num[35]', + 'test_50.py::test_num[36]', + 'test_50.py::test_num[37]', + 'test_50.py::test_num[38]', + 'test_50.py::test_num[39]', + 'test_50.py::test_num[3]', + 'test_50.py::test_num[40]', + 'test_50.py::test_num[41]', + 'test_50.py::test_num[42]', + 'test_50.py::test_num[43]', + 'test_50.py::test_num[44]', + 'test_50.py::test_num[45]', + 'test_50.py::test_num[46]', + 'test_50.py::test_num[47]', + 'test_50.py::test_num[48]', + 'test_50.py::test_num[49]', + 'test_50.py::test_num[4]', + 'test_50.py::test_num[5]', + 'test_50.py::test_num[6]', + 'test_50.py::test_num[7]', + 'test_50.py::test_num[8]', + 'test_50.py::test_num[9]', + 'test_assert1.py::test_function', + 'test_assert2.py::test_set_comparison', + 'test_caching.py::test_function', + 'test_foocompare.py::test_compare'] cache/stepwise contains: [] example/value contains: 42 - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== ``--cache-show`` takes an optional argument to specify a glob pattern for filtering: @@ -284,7 +358,7 @@ filtering: $ pytest --cache-show example/* =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR cachedir: $PYTHON_PREFIX/.pytest_cache @@ -292,7 +366,7 @@ filtering: example/value contains: 42 - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== Clearing Cache content ---------------------- diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/capture.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/capture.rst index 90403d23ab7..caaebdf81a8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/capture.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/capture.rst @@ -21,27 +21,36 @@ file descriptors. This allows to capture output from simple print statements as well as output from a subprocess started by a test. +.. _capture-method: + Setting capturing methods or disabling capturing ------------------------------------------------- -There are two ways in which ``pytest`` can perform capturing: +There are three ways in which ``pytest`` can perform capturing: -* file descriptor (FD) level capturing (default): All writes going to the +* ``fd`` (file descriptor) level capturing (default): All writes going to the operating system file descriptors 1 and 2 will be captured. * ``sys`` level capturing: Only writes to Python files ``sys.stdout`` and ``sys.stderr`` will be captured. No capturing of writes to filedescriptors is performed. +* ``tee-sys`` capturing: Python writes to ``sys.stdout`` and ``sys.stderr`` + will be captured, however the writes will also be passed-through to + the actual ``sys.stdout`` and ``sys.stderr``. This allows output to be + 'live printed' and captured for plugin use, such as junitxml (new in pytest 5.4). + .. _`disable capturing`: You can influence output capturing mechanisms from the command line: .. code-block:: bash - pytest -s # disable all capturing - pytest --capture=sys # replace sys.stdout/stderr with in-mem files - pytest --capture=fd # also point filedescriptors 1 and 2 to temp file + pytest -s # disable all capturing + pytest --capture=sys # replace sys.stdout/stderr with in-mem files + pytest --capture=fd # also point filedescriptors 1 and 2 to temp file + pytest --capture=tee-sys # combines 'sys' and '-s', capturing sys.stdout/stderr + # and passing it along to the actual sys.stdout/stderr .. _printdebugging: @@ -49,16 +58,21 @@ Using print statements for debugging --------------------------------------------------- One primary benefit of the default capturing of stdout/stderr output -is that you can use print statements for debugging:: +is that you can use print statements for debugging: + +.. code-block:: python # content of test_module.py + def setup_function(function): - print("setting up %s" % function) + print("setting up", function) + def test_func1(): assert True + def test_func2(): assert False @@ -69,7 +83,7 @@ of the failing function and hide the other one: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items @@ -83,10 +97,12 @@ of the failing function and hide the other one: > assert False E assert False - test_module.py:9: AssertionError + test_module.py:12: AssertionError -------------------------- Captured stdout setup --------------------------- setting up <function test_func2 at 0xdeadbeef> - ==================== 1 failed, 1 passed in 0.12 seconds ==================== + ========================= short test summary info ========================== + FAILED test_module.py::test_func2 - assert False + ======================= 1 failed, 1 passed in 0.12s ======================== Accessing captured output from a test function --------------------------------------------------- @@ -129,8 +145,7 @@ The return value from ``readouterr`` changed to a ``namedtuple`` with two attrib If the code under test writes non-textual data, you can capture this using the ``capsysbinary`` fixture which instead returns ``bytes`` from -the ``readouterr`` method. The ``capfsysbinary`` fixture is currently only -available in python 3. +the ``readouterr`` method. @@ -154,5 +169,3 @@ as a context manager, disabling capture inside the ``with`` block: with capsys.disabled(): print("output not captured, going directly to sys.stdout") print("this output is also captured") - -.. include:: links.inc diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst index f9a219a92ae..2a26b5c3fb9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/changelog.rst @@ -1,4 +1,8668 @@ +.. _`changelog`: -.. _changelog: +========= +Changelog +========= -.. include:: ../../CHANGELOG.rst +Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``). + +Backward incompatible (breaking) changes will only be introduced in major versions +with advance notice in the **Deprecations** section of releases. + + +.. + You should *NOT* be adding new change log entries to this file, this + file is managed by towncrier. You *may* edit previous change logs to + fix problems like typo corrections or such. + To add a new change log entry, please see + https://pip.pypa.io/en/latest/development/contributing/#news-entries + we named the news folder changelog + + +.. only:: changelog_towncrier_draft + + .. The 'changelog_towncrier_draft' tag is included by our 'tox -e docs', + but not on readthedocs. + + .. include:: _changelog_towncrier_draft.rst + +.. towncrier release notes start + +pytest 6.1.1 (2020-10-03) +========================= + +Bug Fixes +--------- + +- `#7807 <https://github.com/pytest-dev/pytest/issues/7807>`_: Fixed regression in pytest 6.1.0 causing incorrect rootdir to be determined in some non-trivial cases where parent directories have config files as well. + + +- `#7814 <https://github.com/pytest-dev/pytest/issues/7814>`_: Fixed crash in header reporting when :confval:`testpaths` is used and contains absolute paths (regression in 6.1.0). + + +pytest 6.1.0 (2020-09-26) +========================= + +Breaking Changes +---------------- + +- `#5585 <https://github.com/pytest-dev/pytest/issues/5585>`_: As per our policy, the following features which have been deprecated in the 5.X series are now + removed: + + * The ``funcargnames`` read-only property of ``FixtureRequest``, ``Metafunc``, and ``Function`` classes. Use ``fixturenames`` attribute. + + * ``@pytest.fixture`` no longer supports positional arguments, pass all arguments by keyword instead. + + * Direct construction of ``Node`` subclasses now raise an error, use ``from_parent`` instead. + + * The default value for ``junit_family`` has changed to ``xunit2``. If you require the old format, add ``junit_family=xunit1`` to your configuration file. + + * The ``TerminalReporter`` no longer has a ``writer`` attribute. Plugin authors may use the public functions of the ``TerminalReporter`` instead of accessing the ``TerminalWriter`` object directly. + + * The ``--result-log`` option has been removed. Users are recommended to use the `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin instead. + + + For more information consult + `Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`__ in the docs. + + + +Deprecations +------------ + +- `#6981 <https://github.com/pytest-dev/pytest/issues/6981>`_: The ``pytest.collect`` module is deprecated: all its names can be imported from ``pytest`` directly. + + +- `#7097 <https://github.com/pytest-dev/pytest/issues/7097>`_: The ``pytest._fillfuncargs`` function is deprecated. This function was kept + for backward compatibility with an older plugin. + + It's functionality is not meant to be used directly, but if you must replace + it, use `function._request._fillfixtures()` instead, though note this is not + a public API and may break in the future. + + +- `#7210 <https://github.com/pytest-dev/pytest/issues/7210>`_: The special ``-k '-expr'`` syntax to ``-k`` is deprecated. Use ``-k 'not expr'`` + instead. + + The special ``-k 'expr:'`` syntax to ``-k`` is deprecated. Please open an issue + if you use this and want a replacement. + + +- `#7255 <https://github.com/pytest-dev/pytest/issues/7255>`_: The :func:`pytest_warning_captured <_pytest.hookspec.pytest_warning_captured>` hook is deprecated in favor + of :func:`pytest_warning_recorded <_pytest.hookspec.pytest_warning_recorded>`, and will be removed in a future version. + + +- `#7648 <https://github.com/pytest-dev/pytest/issues/7648>`_: The ``gethookproxy()`` and ``isinitpath()`` methods of ``FSCollector`` and ``Package`` are deprecated; + use ``self.session.gethookproxy()`` and ``self.session.isinitpath()`` instead. + This should work on all pytest versions. + + + +Features +-------- + +- `#7667 <https://github.com/pytest-dev/pytest/issues/7667>`_: New ``--durations-min`` command-line flag controls the minimal duration for inclusion in the slowest list of tests shown by ``--durations``. Previously this was hard-coded to ``0.005s``. + + + +Improvements +------------ + +- `#6681 <https://github.com/pytest-dev/pytest/issues/6681>`_: Internal pytest warnings issued during the early stages of initialization are now properly handled and can filtered through :confval:`filterwarnings` or ``--pythonwarnings/-W``. + + This also fixes a number of long standing issues: `#2891 <https://github.com/pytest-dev/pytest/issues/2891>`__, `#7620 <https://github.com/pytest-dev/pytest/issues/7620>`__, `#7426 <https://github.com/pytest-dev/pytest/issues/7426>`__. + + +- `#7572 <https://github.com/pytest-dev/pytest/issues/7572>`_: When a plugin listed in ``required_plugins`` is missing or an unknown config key is used with ``--strict-config``, a simple error message is now shown instead of a stacktrace. + + +- `#7685 <https://github.com/pytest-dev/pytest/issues/7685>`_: Added two new attributes :attr:`rootpath <_pytest.config.Config.rootpath>` and :attr:`inipath <_pytest.config.Config.inipath>` to :class:`Config <_pytest.config.Config>`. + These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir <_pytest.config.Config.rootdir>` and :attr:`inifile <_pytest.config.Config.inifile>` attributes, + and should be preferred over them when possible. + + +- `#7780 <https://github.com/pytest-dev/pytest/issues/7780>`_: Public classes which are not designed to be inherited from are now marked `@final <https://docs.python.org/3/library/typing.html#typing.final>`_. + Code which inherits from these classes will trigger a type-checking (e.g. mypy) error, but will still work in runtime. + Currently the ``final`` designation does not appear in the API Reference but hopefully will in the future. + + + +Bug Fixes +--------- + +- `#1953 <https://github.com/pytest-dev/pytest/issues/1953>`_: Fixed error when overwriting a parametrized fixture, while also reusing the super fixture value. + + .. code-block:: python + + # conftest.py + import pytest + + + @pytest.fixture(params=[1, 2]) + def foo(request): + return request.param + + + # test_foo.py + import pytest + + + @pytest.fixture + def foo(foo): + return foo * 2 + + +- `#4984 <https://github.com/pytest-dev/pytest/issues/4984>`_: Fixed an internal error crash with ``IndexError: list index out of range`` when + collecting a module which starts with a decorated function, the decorator + raises, and assertion rewriting is enabled. + + +- `#7591 <https://github.com/pytest-dev/pytest/issues/7591>`_: pylint shouldn't complain anymore about unimplemented abstract methods when inheriting from :ref:`File <non-python tests>`. + + +- `#7628 <https://github.com/pytest-dev/pytest/issues/7628>`_: Fixed test collection when a full path without a drive letter was passed to pytest on Windows (for example ``\projects\tests\test.py`` instead of ``c:\projects\tests\pytest.py``). + + +- `#7638 <https://github.com/pytest-dev/pytest/issues/7638>`_: Fix handling of command-line options that appear as paths but trigger an OS-level syntax error on Windows, such as the options used internally by ``pytest-xdist``. + + +- `#7742 <https://github.com/pytest-dev/pytest/issues/7742>`_: Fixed INTERNALERROR when accessing locals / globals with faulty ``exec``. + + + +Improved Documentation +---------------------- + +- `#1477 <https://github.com/pytest-dev/pytest/issues/1477>`_: Removed faq.rst and its reference in contents.rst. + + + +Trivial/Internal Changes +------------------------ + +- `#7536 <https://github.com/pytest-dev/pytest/issues/7536>`_: The internal ``junitxml`` plugin has rewritten to use ``xml.etree.ElementTree``. + The order of attributes in XML elements might differ. Some unneeded escaping is + no longer performed. + + +- `#7587 <https://github.com/pytest-dev/pytest/issues/7587>`_: The dependency on the ``more-itertools`` package has been removed. + + +- `#7631 <https://github.com/pytest-dev/pytest/issues/7631>`_: The result type of :meth:`capfd.readouterr() <_pytest.capture.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple, + but should behave like one in all respects. This was done for technical reasons. + + +- `#7671 <https://github.com/pytest-dev/pytest/issues/7671>`_: When collecting tests, pytest finds test classes and functions by examining the + attributes of python objects (modules, classes and instances). To speed up this + process, pytest now ignores builtin attributes (like ``__class__``, + ``__delattr__`` and ``__new__``) without consulting the :confval:`python_classes` and + :confval:`python_functions` configuration options and without passing them to plugins + using the :func:`pytest_pycollect_makeitem <_pytest.hookspec.pytest_pycollect_makeitem>` hook. + + +pytest 6.0.2 (2020-09-04) +========================= + +Bug Fixes +--------- + +- `#7148 <https://github.com/pytest-dev/pytest/issues/7148>`_: Fixed ``--log-cli`` potentially causing unrelated ``print`` output to be swallowed. + + +- `#7672 <https://github.com/pytest-dev/pytest/issues/7672>`_: Fixed log-capturing level restored incorrectly if ``caplog.set_level`` is called more than once. + + +- `#7686 <https://github.com/pytest-dev/pytest/issues/7686>`_: Fixed `NotSetType.token` being used as the parameter ID when the parametrization list is empty. + Regressed in pytest 6.0.0. + + +- `#7707 <https://github.com/pytest-dev/pytest/issues/7707>`_: Fix internal error when handling some exceptions that contain multiple lines or the style uses multiple lines (``--tb=line`` for example). + + +pytest 6.0.1 (2020-07-30) +========================= + +Bug Fixes +--------- + +- `#7394 <https://github.com/pytest-dev/pytest/issues/7394>`_: Passing an empty ``help`` value to ``Parser.add_option`` is now accepted instead of crashing when running ``pytest --help``. + Passing ``None`` raises a more informative ``TypeError``. + + +- `#7558 <https://github.com/pytest-dev/pytest/issues/7558>`_: Fix pylint ``not-callable`` lint on ``pytest.mark.parametrize()`` and the other builtin marks: + ``skip``, ``skipif``, ``xfail``, ``usefixtures``, ``filterwarnings``. + + +- `#7559 <https://github.com/pytest-dev/pytest/issues/7559>`_: Fix regression in plugins using ``TestReport.longreprtext`` (such as ``pytest-html``) when ``TestReport.longrepr`` is not a string. + + +- `#7569 <https://github.com/pytest-dev/pytest/issues/7569>`_: Fix logging capture handler's level not reset on teardown after a call to ``caplog.set_level()``. + + +pytest 6.0.0 (2020-07-28) +========================= + +(**Please see the full set of changes for this release also in the 6.0.0rc1 notes below**) + +Breaking Changes +---------------- + +- `#5584 <https://github.com/pytest-dev/pytest/issues/5584>`_: **PytestDeprecationWarning are now errors by default.** + + Following our plan to remove deprecated features with as little disruption as + possible, all warnings of type ``PytestDeprecationWarning`` now generate errors + instead of warning messages. + + **The affected features will be effectively removed in pytest 6.1**, so please consult the + `Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ + section in the docs for directions on how to update existing code. + + In the pytest ``6.0.X`` series, it is possible to change the errors back into warnings as a + stopgap measure by adding this to your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + filterwarnings = + ignore::pytest.PytestDeprecationWarning + + But this will stop working when pytest ``6.1`` is released. + + **If you have concerns** about the removal of a specific feature, please add a + comment to `#5584 <https://github.com/pytest-dev/pytest/issues/5584>`__. + + +- `#7472 <https://github.com/pytest-dev/pytest/issues/7472>`_: The ``exec_()`` and ``is_true()`` methods of ``_pytest._code.Frame`` have been removed. + + + +Features +-------- + +- `#7464 <https://github.com/pytest-dev/pytest/issues/7464>`_: Added support for :envvar:`NO_COLOR` and :envvar:`FORCE_COLOR` environment variables to control colored output. + + + +Improvements +------------ + +- `#7467 <https://github.com/pytest-dev/pytest/issues/7467>`_: ``--log-file`` CLI option and ``log_file`` ini marker now create subdirectories if needed. + + +- `#7489 <https://github.com/pytest-dev/pytest/issues/7489>`_: The :func:`pytest.raises` function has a clearer error message when ``match`` equals the obtained string but is not a regex match. In this case it is suggested to escape the regex. + + + +Bug Fixes +--------- + +- `#7392 <https://github.com/pytest-dev/pytest/issues/7392>`_: Fix the reported location of tests skipped with ``@pytest.mark.skip`` when ``--runxfail`` is used. + + +- `#7491 <https://github.com/pytest-dev/pytest/issues/7491>`_: :fixture:`tmpdir` and :fixture:`tmp_path` no longer raise an error if the lock to check for + stale temporary directories is not accessible. + + +- `#7517 <https://github.com/pytest-dev/pytest/issues/7517>`_: Preserve line endings when captured via ``capfd``. + + +- `#7534 <https://github.com/pytest-dev/pytest/issues/7534>`_: Restored the previous formatting of ``TracebackEntry.__str__`` which was changed by accident. + + + +Improved Documentation +---------------------- + +- `#7422 <https://github.com/pytest-dev/pytest/issues/7422>`_: Clarified when the ``usefixtures`` mark can apply fixtures to test. + + +- `#7441 <https://github.com/pytest-dev/pytest/issues/7441>`_: Add a note about ``-q`` option used in getting started guide. + + + +Trivial/Internal Changes +------------------------ + +- `#7389 <https://github.com/pytest-dev/pytest/issues/7389>`_: Fixture scope ``package`` is no longer considered experimental. + + +pytest 6.0.0rc1 (2020-07-08) +============================ + +Breaking Changes +---------------- + +- `#1316 <https://github.com/pytest-dev/pytest/issues/1316>`_: ``TestReport.longrepr`` is now always an instance of ``ReprExceptionInfo``. Previously it was a ``str`` when a test failed with ``pytest.fail(..., pytrace=False)``. + + +- `#5965 <https://github.com/pytest-dev/pytest/issues/5965>`_: symlinks are no longer resolved during collection and matching `conftest.py` files with test file paths. + + Resolving symlinks for the current directory and during collection was introduced as a bugfix in 3.9.0, but it actually is a new feature which had unfortunate consequences in Windows and surprising results in other platforms. + + The team decided to step back on resolving symlinks at all, planning to review this in the future with a more solid solution (see discussion in + `#6523 <https://github.com/pytest-dev/pytest/pull/6523>`__ for details). + + This might break test suites which made use of this feature; the fix is to create a symlink + for the entire test tree, and not only to partial files/tress as it was possible previously. + + +- `#6505 <https://github.com/pytest-dev/pytest/issues/6505>`_: ``Testdir.run().parseoutcomes()`` now always returns the parsed nouns in plural form. + + Originally ``parseoutcomes()`` would always returns the nouns in plural form, but a change + meant to improve the terminal summary by using singular form single items (``1 warning`` or ``1 error``) + caused an unintended regression by changing the keys returned by ``parseoutcomes()``. + + Now the API guarantees to always return the plural form, so calls like this: + + .. code-block:: python + + result = testdir.runpytest() + result.assert_outcomes(error=1) + + Need to be changed to: + + + .. code-block:: python + + result = testdir.runpytest() + result.assert_outcomes(errors=1) + + +- `#6903 <https://github.com/pytest-dev/pytest/issues/6903>`_: The ``os.dup()`` function is now assumed to exist. We are not aware of any + supported Python 3 implementations which do not provide it. + + +- `#7040 <https://github.com/pytest-dev/pytest/issues/7040>`_: ``-k`` no longer matches against the names of the directories outside the test session root. + + Also, ``pytest.Package.name`` is now just the name of the directory containing the package's + ``__init__.py`` file, instead of the full path. This is consistent with how the other nodes + are named, and also one of the reasons why ``-k`` would match against any directory containing + the test suite. + + +- `#7122 <https://github.com/pytest-dev/pytest/issues/7122>`_: Expressions given to the ``-m`` and ``-k`` options are no longer evaluated using Python's :func:`eval`. + The format supports ``or``, ``and``, ``not``, parenthesis and general identifiers to match against. + Python constants, keywords or other operators are no longer evaluated differently. + + +- `#7135 <https://github.com/pytest-dev/pytest/issues/7135>`_: Pytest now uses its own ``TerminalWriter`` class instead of using the one from the ``py`` library. + Plugins generally access this class through ``TerminalReporter.writer``, ``TerminalReporter.write()`` + (and similar methods), or ``_pytest.config.create_terminal_writer()``. + + The following breaking changes were made: + + - Output (``write()`` method and others) no longer flush implicitly; the flushing behavior + of the underlying file is respected. To flush explicitly (for example, if you + want output to be shown before an end-of-line is printed), use ``write(flush=True)`` or + ``terminal_writer.flush()``. + - Explicit Windows console support was removed, delegated to the colorama library. + - Support for writing ``bytes`` was removed. + - The ``reline`` method and ``chars_on_current_line`` property were removed. + - The ``stringio`` and ``encoding`` arguments was removed. + - Support for passing a callable instead of a file was removed. + + +- `#7224 <https://github.com/pytest-dev/pytest/issues/7224>`_: The `item.catch_log_handler` and `item.catch_log_handlers` attributes, set by the + logging plugin and never meant to be public, are no longer available. + + The deprecated ``--no-print-logs`` option and ``log_print`` ini option are removed. Use ``--show-capture`` instead. + + +- `#7226 <https://github.com/pytest-dev/pytest/issues/7226>`_: Removed the unused ``args`` parameter from ``pytest.Function.__init__``. + + +- `#7418 <https://github.com/pytest-dev/pytest/issues/7418>`_: Removed the `pytest_doctest_prepare_content` hook specification. This hook + hasn't been triggered by pytest for at least 10 years. + + +- `#7438 <https://github.com/pytest-dev/pytest/issues/7438>`_: Some changes were made to the internal ``_pytest._code.source``, listed here + for the benefit of plugin authors who may be using it: + + - The ``deindent`` argument to ``Source()`` has been removed, now it is always true. + - Support for zero or multiple arguments to ``Source()`` has been removed. + - Support for comparing ``Source`` with an ``str`` has been removed. + - The methods ``Source.isparseable()`` and ``Source.putaround()`` have been removed. + - The method ``Source.compile()`` and function ``_pytest._code.compile()`` have + been removed; use plain ``compile()`` instead. + - The function ``_pytest._code.source.getsource()`` has been removed; use + ``Source()`` directly instead. + + + +Deprecations +------------ + +- `#7210 <https://github.com/pytest-dev/pytest/issues/7210>`_: The special ``-k '-expr'`` syntax to ``-k`` is deprecated. Use ``-k 'not expr'`` + instead. + + The special ``-k 'expr:'`` syntax to ``-k`` is deprecated. Please open an issue + if you use this and want a replacement. + +- `#4049 <https://github.com/pytest-dev/pytest/issues/4049>`_: ``pytest_warning_captured`` is deprecated in favor of the ``pytest_warning_recorded`` hook. + + +Features +-------- + +- `#1556 <https://github.com/pytest-dev/pytest/issues/1556>`_: pytest now supports ``pyproject.toml`` files for configuration. + + The configuration options is similar to the one available in other formats, but must be defined + in a ``[tool.pytest.ini_options]`` table to be picked up by pytest: + + .. code-block:: toml + + # pyproject.toml + [tool.pytest.ini_options] + minversion = "6.0" + addopts = "-ra -q" + testpaths = [ + "tests", + "integration", + ] + + More information can be found `in the docs <https://docs.pytest.org/en/stable/customize.html#configuration-file-formats>`__. + + +- `#3342 <https://github.com/pytest-dev/pytest/issues/3342>`_: pytest now includes inline type annotations and exposes them to user programs. + Most of the user-facing API is covered, as well as internal code. + + If you are running a type checker such as mypy on your tests, you may start + noticing type errors indicating incorrect usage. If you run into an error that + you believe to be incorrect, please let us know in an issue. + + The types were developed against mypy version 0.780. Versions before 0.750 + are known not to work. We recommend using the latest version. Other type + checkers may work as well, but they are not officially verified to work by + pytest yet. + + +- `#4049 <https://github.com/pytest-dev/pytest/issues/4049>`_: Introduced a new hook named `pytest_warning_recorded` to convey information about warnings captured by the internal `pytest` warnings plugin. + + This hook is meant to replace `pytest_warning_captured`, which is deprecated and will be removed in a future release. + + +- `#6471 <https://github.com/pytest-dev/pytest/issues/6471>`_: New command-line flags: + + * `--no-header`: disables the initial header, including platform, version, and plugins. + * `--no-summary`: disables the final test summary, including warnings. + + +- `#6856 <https://github.com/pytest-dev/pytest/issues/6856>`_: A warning is now shown when an unknown key is read from a config INI file. + + The `--strict-config` flag has been added to treat these warnings as errors. + + +- `#6906 <https://github.com/pytest-dev/pytest/issues/6906>`_: Added `--code-highlight` command line option to enable/disable code highlighting in terminal output. + + +- `#7245 <https://github.com/pytest-dev/pytest/issues/7245>`_: New ``--import-mode=importlib`` option that uses `importlib <https://docs.python.org/3/library/importlib.html>`__ to import test modules. + + Traditionally pytest used ``__import__`` while changing ``sys.path`` to import test modules (which + also changes ``sys.modules`` as a side-effect), which works but has a number of drawbacks, like requiring test modules + that don't live in packages to have unique names (as they need to reside under a unique name in ``sys.modules``). + + ``--import-mode=importlib`` uses more fine grained import mechanisms from ``importlib`` which don't + require pytest to change ``sys.path`` or ``sys.modules`` at all, eliminating much of the drawbacks + of the previous mode. + + We intend to make ``--import-mode=importlib`` the default in future versions, so users are encouraged + to try the new mode and provide feedback (both positive or negative) in issue `#7245 <https://github.com/pytest-dev/pytest/issues/7245>`__. + + You can read more about this option in `the documentation <https://docs.pytest.org/en/latest/pythonpath.html#import-modes>`__. + + +- `#7305 <https://github.com/pytest-dev/pytest/issues/7305>`_: New ``required_plugins`` configuration option allows the user to specify a list of plugins, including version information, that are required for pytest to run. An error is raised if any required plugins are not found when running pytest. + + +Improvements +------------ + +- `#4375 <https://github.com/pytest-dev/pytest/issues/4375>`_: The ``pytest`` command now suppresses the ``BrokenPipeError`` error message that + is printed to stderr when the output of ``pytest`` is piped and and the pipe is + closed by the piped-to program (common examples are ``less`` and ``head``). + + +- `#4391 <https://github.com/pytest-dev/pytest/issues/4391>`_: Improved precision of test durations measurement. ``CallInfo`` items now have a new ``<CallInfo>.duration`` attribute, created using ``time.perf_counter()``. This attribute is used to fill the ``<TestReport>.duration`` attribute, which is more accurate than the previous ``<CallInfo>.stop - <CallInfo>.start`` (as these are based on ``time.time()``). + + +- `#4675 <https://github.com/pytest-dev/pytest/issues/4675>`_: Rich comparison for dataclasses and `attrs`-classes is now recursive. + + +- `#6285 <https://github.com/pytest-dev/pytest/issues/6285>`_: Exposed the `pytest.FixtureLookupError` exception which is raised by `request.getfixturevalue()` + (where `request` is a `FixtureRequest` fixture) when a fixture with the given name cannot be returned. + + +- `#6433 <https://github.com/pytest-dev/pytest/issues/6433>`_: If an error is encountered while formatting the message in a logging call, for + example ``logging.warning("oh no!: %s: %s", "first")`` (a second argument is + missing), pytest now propagates the error, likely causing the test to fail. + + Previously, such a mistake would cause an error to be printed to stderr, which + is not displayed by default for passing tests. This change makes the mistake + visible during testing. + + You may supress this behavior temporarily or permanently by setting + ``logging.raiseExceptions = False``. + + +- `#6817 <https://github.com/pytest-dev/pytest/issues/6817>`_: Explicit new-lines in help texts of command-line options are preserved, allowing plugins better control + of the help displayed to users. + + +- `#6940 <https://github.com/pytest-dev/pytest/issues/6940>`_: When using the ``--duration`` option, the terminal message output is now more precise about the number and duration of hidden items. + + +- `#6991 <https://github.com/pytest-dev/pytest/issues/6991>`_: Collected files are displayed after any reports from hooks, e.g. the status from ``--lf``. + + +- `#7091 <https://github.com/pytest-dev/pytest/issues/7091>`_: When ``fd`` capturing is used, through ``--capture=fd`` or the ``capfd`` and + ``capfdbinary`` fixtures, and the file descriptor (0, 1, 2) cannot be + duplicated, FD capturing is still performed. Previously, direct writes to the + file descriptors would fail or be lost in this case. + + +- `#7119 <https://github.com/pytest-dev/pytest/issues/7119>`_: Exit with an error if the ``--basetemp`` argument is empty, is the current working directory or is one of the parent directories. + This is done to protect against accidental data loss, as any directory passed to this argument is cleared. + + +- `#7128 <https://github.com/pytest-dev/pytest/issues/7128>`_: `pytest --version` now displays just the pytest version, while `pytest --version --version` displays more verbose information including plugins. This is more consistent with how other tools show `--version`. + + +- `#7133 <https://github.com/pytest-dev/pytest/issues/7133>`_: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` will now override any :confval:`log_level` set via the CLI or configuration file. + + +- `#7159 <https://github.com/pytest-dev/pytest/issues/7159>`_: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` and :meth:`caplog.at_level() <_pytest.logging.LogCaptureFixture.at_level>` no longer affect + the level of logs that are shown in the *Captured log report* report section. + + +- `#7348 <https://github.com/pytest-dev/pytest/issues/7348>`_: Improve recursive diff report for comparison asserts on dataclasses / attrs. + + +- `#7385 <https://github.com/pytest-dev/pytest/issues/7385>`_: ``--junitxml`` now includes the exception cause in the ``message`` XML attribute for failures during setup and teardown. + + Previously: + + .. code-block:: xml + + <error message="test setup failure"> + + Now: + + .. code-block:: xml + + <error message="failed on setup with "ValueError: Some error during setup""> + + + +Bug Fixes +--------- + +- `#1120 <https://github.com/pytest-dev/pytest/issues/1120>`_: Fix issue where directories from :fixture:`tmpdir` are not removed properly when multiple instances of pytest are running in parallel. + + +- `#4583 <https://github.com/pytest-dev/pytest/issues/4583>`_: Prevent crashing and provide a user-friendly error when a marker expression (`-m`) invoking of :func:`eval` raises any exception. + + +- `#4677 <https://github.com/pytest-dev/pytest/issues/4677>`_: The path shown in the summary report for SKIPPED tests is now always relative. Previously it was sometimes absolute. + + +- `#5456 <https://github.com/pytest-dev/pytest/issues/5456>`_: Fix a possible race condition when trying to remove lock files used to control access to folders + created by :fixture:`tmp_path` and :fixture:`tmpdir`. + + +- `#6240 <https://github.com/pytest-dev/pytest/issues/6240>`_: Fixes an issue where logging during collection step caused duplication of log + messages to stderr. + + +- `#6428 <https://github.com/pytest-dev/pytest/issues/6428>`_: Paths appearing in error messages are now correct in case the current working directory has + changed since the start of the session. + + +- `#6755 <https://github.com/pytest-dev/pytest/issues/6755>`_: Support deleting paths longer than 260 characters on windows created inside :fixture:`tmpdir`. + + +- `#6871 <https://github.com/pytest-dev/pytest/issues/6871>`_: Fix crash with captured output when using :fixture:`capsysbinary`. + + +- `#6909 <https://github.com/pytest-dev/pytest/issues/6909>`_: Revert the change introduced by `#6330 <https://github.com/pytest-dev/pytest/pull/6330>`_, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature. + + The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted. + + +- `#6910 <https://github.com/pytest-dev/pytest/issues/6910>`_: Fix crash when plugins return an unknown stats while using the ``--reportlog`` option. + + +- `#6924 <https://github.com/pytest-dev/pytest/issues/6924>`_: Ensure a ``unittest.IsolatedAsyncioTestCase`` is actually awaited. + + +- `#6925 <https://github.com/pytest-dev/pytest/issues/6925>`_: Fix `TerminalRepr` instances to be hashable again. + + +- `#6947 <https://github.com/pytest-dev/pytest/issues/6947>`_: Fix regression where functions registered with :meth:`unittest.TestCase.addCleanup` were not being called on test failures. + + +- `#6951 <https://github.com/pytest-dev/pytest/issues/6951>`_: Allow users to still set the deprecated ``TerminalReporter.writer`` attribute. + + +- `#6956 <https://github.com/pytest-dev/pytest/issues/6956>`_: Prevent pytest from printing `ConftestImportFailure` traceback to stdout. + + +- `#6991 <https://github.com/pytest-dev/pytest/issues/6991>`_: Fix regressions with `--lf` filtering too much since pytest 5.4. + + +- `#6992 <https://github.com/pytest-dev/pytest/issues/6992>`_: Revert "tmpdir: clean up indirection via config for factories" `#6767 <https://github.com/pytest-dev/pytest/issues/6767>`_ as it breaks pytest-xdist. + + +- `#7061 <https://github.com/pytest-dev/pytest/issues/7061>`_: When a yielding fixture fails to yield a value, report a test setup error instead of crashing. + + +- `#7076 <https://github.com/pytest-dev/pytest/issues/7076>`_: The path of file skipped by ``@pytest.mark.skip`` in the SKIPPED report is now relative to invocation directory. Previously it was relative to root directory. + + +- `#7110 <https://github.com/pytest-dev/pytest/issues/7110>`_: Fixed regression: ``asyncbase.TestCase`` tests are executed correctly again. + + +- `#7126 <https://github.com/pytest-dev/pytest/issues/7126>`_: ``--setup-show`` now doesn't raise an error when a bytes value is used as a ``parametrize`` + parameter when Python is called with the ``-bb`` flag. + + +- `#7143 <https://github.com/pytest-dev/pytest/issues/7143>`_: Fix :meth:`pytest.File.from_parent` so it forwards extra keyword arguments to the constructor. + + +- `#7145 <https://github.com/pytest-dev/pytest/issues/7145>`_: Classes with broken ``__getattribute__`` methods are displayed correctly during failures. + + +- `#7150 <https://github.com/pytest-dev/pytest/issues/7150>`_: Prevent hiding the underlying exception when ``ConfTestImportFailure`` is raised. + + +- `#7180 <https://github.com/pytest-dev/pytest/issues/7180>`_: Fix ``_is_setup_py`` for files encoded differently than locale. + + +- `#7215 <https://github.com/pytest-dev/pytest/issues/7215>`_: Fix regression where running with ``--pdb`` would call :meth:`unittest.TestCase.tearDown` for skipped tests. + + +- `#7253 <https://github.com/pytest-dev/pytest/issues/7253>`_: When using ``pytest.fixture`` on a function directly, as in ``pytest.fixture(func)``, + if the ``autouse`` or ``params`` arguments are also passed, the function is no longer + ignored, but is marked as a fixture. + + +- `#7360 <https://github.com/pytest-dev/pytest/issues/7360>`_: Fix possibly incorrect evaluation of string expressions passed to ``pytest.mark.skipif`` and ``pytest.mark.xfail``, + in rare circumstances where the exact same string is used but refers to different global values. + + +- `#7383 <https://github.com/pytest-dev/pytest/issues/7383>`_: Fixed exception causes all over the codebase, i.e. use `raise new_exception from old_exception` when wrapping an exception. + + + +Improved Documentation +---------------------- + +- `#7202 <https://github.com/pytest-dev/pytest/issues/7202>`_: The development guide now links to the contributing section of the docs and `RELEASING.rst` on GitHub. + + +- `#7233 <https://github.com/pytest-dev/pytest/issues/7233>`_: Add a note about ``--strict`` and ``--strict-markers`` and the preference for the latter one. + + +- `#7345 <https://github.com/pytest-dev/pytest/issues/7345>`_: Explain indirect parametrization and markers for fixtures. + + + +Trivial/Internal Changes +------------------------ + +- `#7035 <https://github.com/pytest-dev/pytest/issues/7035>`_: The ``originalname`` attribute of ``_pytest.python.Function`` now defaults to ``name`` if not + provided explicitly, and is always set. + + +- `#7264 <https://github.com/pytest-dev/pytest/issues/7264>`_: The dependency on the ``wcwidth`` package has been removed. + + +- `#7291 <https://github.com/pytest-dev/pytest/issues/7291>`_: Replaced ``py.iniconfig`` with `iniconfig <https://pypi.org/project/iniconfig/>`__. + + +- `#7295 <https://github.com/pytest-dev/pytest/issues/7295>`_: ``src/_pytest/config/__init__.py`` now uses the ``warnings`` module to report warnings instead of ``sys.stderr.write``. + + +- `#7356 <https://github.com/pytest-dev/pytest/issues/7356>`_: Remove last internal uses of deprecated *slave* term from old ``pytest-xdist``. + + +- `#7357 <https://github.com/pytest-dev/pytest/issues/7357>`_: ``py``>=1.8.2 is now required. + + +pytest 5.4.3 (2020-06-02) +========================= + +Bug Fixes +--------- + +- `#6428 <https://github.com/pytest-dev/pytest/issues/6428>`_: Paths appearing in error messages are now correct in case the current working directory has + changed since the start of the session. + + +- `#6755 <https://github.com/pytest-dev/pytest/issues/6755>`_: Support deleting paths longer than 260 characters on windows created inside tmpdir. + + +- `#6956 <https://github.com/pytest-dev/pytest/issues/6956>`_: Prevent pytest from printing ConftestImportFailure traceback to stdout. + + +- `#7150 <https://github.com/pytest-dev/pytest/issues/7150>`_: Prevent hiding the underlying exception when ``ConfTestImportFailure`` is raised. + + +- `#7215 <https://github.com/pytest-dev/pytest/issues/7215>`_: Fix regression where running with ``--pdb`` would call the ``tearDown`` methods of ``unittest.TestCase`` + subclasses for skipped tests. + + +pytest 5.4.2 (2020-05-08) +========================= + +Bug Fixes +--------- + +- `#6871 <https://github.com/pytest-dev/pytest/issues/6871>`_: Fix crash with captured output when using the :fixture:`capsysbinary fixture <capsysbinary>`. + + +- `#6924 <https://github.com/pytest-dev/pytest/issues/6924>`_: Ensure a ``unittest.IsolatedAsyncioTestCase`` is actually awaited. + + +- `#6925 <https://github.com/pytest-dev/pytest/issues/6925>`_: Fix TerminalRepr instances to be hashable again. + + +- `#6947 <https://github.com/pytest-dev/pytest/issues/6947>`_: Fix regression where functions registered with ``TestCase.addCleanup`` were not being called on test failures. + + +- `#6951 <https://github.com/pytest-dev/pytest/issues/6951>`_: Allow users to still set the deprecated ``TerminalReporter.writer`` attribute. + + +- `#6992 <https://github.com/pytest-dev/pytest/issues/6992>`_: Revert "tmpdir: clean up indirection via config for factories" #6767 as it breaks pytest-xdist. + + +- `#7110 <https://github.com/pytest-dev/pytest/issues/7110>`_: Fixed regression: ``asyncbase.TestCase`` tests are executed correctly again. + + +- `#7143 <https://github.com/pytest-dev/pytest/issues/7143>`_: Fix ``File.from_constructor`` so it forwards extra keyword arguments to the constructor. + + +- `#7145 <https://github.com/pytest-dev/pytest/issues/7145>`_: Classes with broken ``__getattribute__`` methods are displayed correctly during failures. + + +- `#7180 <https://github.com/pytest-dev/pytest/issues/7180>`_: Fix ``_is_setup_py`` for files encoded differently than locale. + + +pytest 5.4.1 (2020-03-13) +========================= + +Bug Fixes +--------- + +- `#6909 <https://github.com/pytest-dev/pytest/issues/6909>`_: Revert the change introduced by `#6330 <https://github.com/pytest-dev/pytest/pull/6330>`_, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature. + + The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted. + + +- `#6910 <https://github.com/pytest-dev/pytest/issues/6910>`_: Fix crash when plugins return an unknown stats while using the ``--reportlog`` option. + + +pytest 5.4.0 (2020-03-12) +========================= + +Breaking Changes +---------------- + +- `#6316 <https://github.com/pytest-dev/pytest/issues/6316>`_: Matching of ``-k EXPRESSION`` to test names is now case-insensitive. + + +- `#6443 <https://github.com/pytest-dev/pytest/issues/6443>`_: Plugins specified with ``-p`` are now loaded after internal plugins, which results in their hooks being called *before* the internal ones. + + This makes the ``-p`` behavior consistent with ``PYTEST_PLUGINS``. + + +- `#6637 <https://github.com/pytest-dev/pytest/issues/6637>`_: Removed the long-deprecated ``pytest_itemstart`` hook. + + This hook has been marked as deprecated and not been even called by pytest for over 10 years now. + + +- `#6673 <https://github.com/pytest-dev/pytest/issues/6673>`_: Reversed / fix meaning of "+/-" in error diffs. "-" means that sth. expected is missing in the result and "+" means that there are unexpected extras in the result. + + +- `#6737 <https://github.com/pytest-dev/pytest/issues/6737>`_: The ``cached_result`` attribute of ``FixtureDef`` is now set to ``None`` when + the result is unavailable, instead of being deleted. + + If your plugin performs checks like ``hasattr(fixturedef, 'cached_result')``, + for example in a ``pytest_fixture_post_finalizer`` hook implementation, replace + it with ``fixturedef.cached_result is not None``. If you ``del`` the attribute, + set it to ``None`` instead. + + + +Deprecations +------------ + +- `#3238 <https://github.com/pytest-dev/pytest/issues/3238>`_: Option ``--no-print-logs`` is deprecated and meant to be removed in a future release. If you use ``--no-print-logs``, please try out ``--show-capture`` and + provide feedback. + + ``--show-capture`` command-line option was added in ``pytest 3.5.0`` and allows to specify how to + display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). + + +- `#571 <https://github.com/pytest-dev/pytest/issues/571>`_: Deprecate the unused/broken `pytest_collect_directory` hook. + It was misaligned since the removal of the ``Directory`` collector in 2010 + and incorrect/unusable as soon as collection was split from test execution. + + +- `#5975 <https://github.com/pytest-dev/pytest/issues/5975>`_: Deprecate using direct constructors for ``Nodes``. + + Instead they are now constructed via ``Node.from_parent``. + + This transitional mechanism enables us to untangle the very intensely + entangled ``Node`` relationships by enforcing more controlled creation/configuration patterns. + + As part of this change, session/config are already disallowed parameters and as we work on the details we might need disallow a few more as well. + + Subclasses are expected to use `super().from_parent` if they intend to expand the creation of `Nodes`. + + +- `#6779 <https://github.com/pytest-dev/pytest/issues/6779>`_: The ``TerminalReporter.writer`` attribute has been deprecated and should no longer be used. This + was inadvertently exposed as part of the public API of that plugin and ties it too much + with ``py.io.TerminalWriter``. + + + +Features +-------- + +- `#4597 <https://github.com/pytest-dev/pytest/issues/4597>`_: New :ref:`--capture=tee-sys <capture-method>` option to allow both live printing and capturing of test output. + + +- `#5712 <https://github.com/pytest-dev/pytest/issues/5712>`_: Now all arguments to ``@pytest.mark.parametrize`` need to be explicitly declared in the function signature or via ``indirect``. + Previously it was possible to omit an argument if a fixture with the same name existed, which was just an accident of implementation and was not meant to be a part of the API. + + +- `#6454 <https://github.com/pytest-dev/pytest/issues/6454>`_: Changed default for `-r` to `fE`, which displays failures and errors in the :ref:`short test summary <pytest.detailed_failed_tests_usage>`. `-rN` can be used to disable it (the old behavior). + + +- `#6469 <https://github.com/pytest-dev/pytest/issues/6469>`_: New options have been added to the :confval:`junit_logging` option: ``log``, ``out-err``, and ``all``. + + +- `#6834 <https://github.com/pytest-dev/pytest/issues/6834>`_: Excess warning summaries are now collapsed per file to ensure readable display of warning summaries. + + + +Improvements +------------ + +- `#1857 <https://github.com/pytest-dev/pytest/issues/1857>`_: ``pytest.mark.parametrize`` accepts integers for ``ids`` again, converting it to strings. + + +- `#449 <https://github.com/pytest-dev/pytest/issues/449>`_: Use "yellow" main color with any XPASSED tests. + + +- `#4639 <https://github.com/pytest-dev/pytest/issues/4639>`_: Revert "A warning is now issued when assertions are made for ``None``". + + The warning proved to be less useful than initially expected and had quite a + few false positive cases. + + +- `#5686 <https://github.com/pytest-dev/pytest/issues/5686>`_: ``tmpdir_factory.mktemp`` now fails when given absolute and non-normalized paths. + + +- `#5984 <https://github.com/pytest-dev/pytest/issues/5984>`_: The ``pytest_warning_captured`` hook now receives a ``location`` parameter with the code location that generated the warning. + + +- `#6213 <https://github.com/pytest-dev/pytest/issues/6213>`_: pytester: the ``testdir`` fixture respects environment settings from the ``monkeypatch`` fixture for inner runs. + + +- `#6247 <https://github.com/pytest-dev/pytest/issues/6247>`_: ``--fulltrace`` is honored with collection errors. + + +- `#6384 <https://github.com/pytest-dev/pytest/issues/6384>`_: Make `--showlocals` work also with `--tb=short`. + + +- `#6653 <https://github.com/pytest-dev/pytest/issues/6653>`_: Add support for matching lines consecutively with :attr:`LineMatcher <_pytest.pytester.LineMatcher>`'s :func:`~_pytest.pytester.LineMatcher.fnmatch_lines` and :func:`~_pytest.pytester.LineMatcher.re_match_lines`. + + +- `#6658 <https://github.com/pytest-dev/pytest/issues/6658>`_: Code is now highlighted in tracebacks when ``pygments`` is installed. + + Users are encouraged to install ``pygments`` into their environment and provide feedback, because + the plan is to make ``pygments`` a regular dependency in the future. + + +- `#6795 <https://github.com/pytest-dev/pytest/issues/6795>`_: Import usage error message with invalid `-o` option. + + +- `#759 <https://github.com/pytest-dev/pytest/issues/759>`_: ``pytest.mark.parametrize`` supports iterators and generators for ``ids``. + + + +Bug Fixes +--------- + +- `#310 <https://github.com/pytest-dev/pytest/issues/310>`_: Add support for calling `pytest.xfail()` and `pytest.importorskip()` with doctests. + + +- `#3823 <https://github.com/pytest-dev/pytest/issues/3823>`_: ``--trace`` now works with unittests. + + +- `#4445 <https://github.com/pytest-dev/pytest/issues/4445>`_: Fixed some warning reports produced by pytest to point to the correct location of the warning in the user's code. + + +- `#5301 <https://github.com/pytest-dev/pytest/issues/5301>`_: Fix ``--last-failed`` to collect new tests from files with known failures. + + +- `#5928 <https://github.com/pytest-dev/pytest/issues/5928>`_: Report ``PytestUnknownMarkWarning`` at the level of the user's code, not ``pytest``'s. + + +- `#5991 <https://github.com/pytest-dev/pytest/issues/5991>`_: Fix interaction with ``--pdb`` and unittests: do not use unittest's ``TestCase.debug()``. + + +- `#6334 <https://github.com/pytest-dev/pytest/issues/6334>`_: Fix summary entries appearing twice when ``f/F`` and ``s/S`` report chars were used at the same time in the ``-r`` command-line option (for example ``-rFf``). + + The upper case variants were never documented and the preferred form should be the lower case. + + +- `#6409 <https://github.com/pytest-dev/pytest/issues/6409>`_: Fallback to green (instead of yellow) for non-last items without previous passes with colored terminal progress indicator. + + +- `#6454 <https://github.com/pytest-dev/pytest/issues/6454>`_: `--disable-warnings` is honored with `-ra` and `-rA`. + + +- `#6497 <https://github.com/pytest-dev/pytest/issues/6497>`_: Fix bug in the comparison of request key with cached key in fixture. + + A construct ``if key == cached_key:`` can fail either because ``==`` is explicitly disallowed, or for, e.g., NumPy arrays, where the result of ``a == b`` cannot generally be converted to `bool`. + The implemented fix replaces `==` with ``is``. + + +- `#6557 <https://github.com/pytest-dev/pytest/issues/6557>`_: Make capture output streams ``.write()`` method return the same return value from original streams. + + +- `#6566 <https://github.com/pytest-dev/pytest/issues/6566>`_: Fix ``EncodedFile.writelines`` to call the underlying buffer's ``writelines`` method. + + +- `#6575 <https://github.com/pytest-dev/pytest/issues/6575>`_: Fix internal crash when ``faulthandler`` starts initialized + (for example with ``PYTHONFAULTHANDLER=1`` environment variable set) and ``faulthandler_timeout`` defined + in the configuration file. + + +- `#6597 <https://github.com/pytest-dev/pytest/issues/6597>`_: Fix node ids which contain a parametrized empty-string variable. + + +- `#6646 <https://github.com/pytest-dev/pytest/issues/6646>`_: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc. + + +- `#6660 <https://github.com/pytest-dev/pytest/issues/6660>`_: :py:func:`pytest.exit` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook. This includes quitting from a debugger. + + +- `#6752 <https://github.com/pytest-dev/pytest/issues/6752>`_: When :py:func:`pytest.raises` is used as a function (as opposed to a context manager), + a `match` keyword argument is now passed through to the tested function. Previously + it was swallowed and ignored (regression in pytest 5.1.0). + + +- `#6801 <https://github.com/pytest-dev/pytest/issues/6801>`_: Do not display empty lines inbetween traceback for unexpected exceptions with doctests. + + +- `#6802 <https://github.com/pytest-dev/pytest/issues/6802>`_: The :fixture:`testdir fixture <testdir>` works within doctests now. + + + +Improved Documentation +---------------------- + +- `#6696 <https://github.com/pytest-dev/pytest/issues/6696>`_: Add list of fixtures to start of fixture chapter. + + +- `#6742 <https://github.com/pytest-dev/pytest/issues/6742>`_: Expand first sentence on fixtures into a paragraph. + + + +Trivial/Internal Changes +------------------------ + +- `#6404 <https://github.com/pytest-dev/pytest/issues/6404>`_: Remove usage of ``parser`` module, deprecated in Python 3.9. + + +pytest 5.3.5 (2020-01-29) +========================= + +Bug Fixes +--------- + +- `#6517 <https://github.com/pytest-dev/pytest/issues/6517>`_: Fix regression in pytest 5.3.4 causing an INTERNALERROR due to a wrong assertion. + + +pytest 5.3.4 (2020-01-20) +========================= + +Bug Fixes +--------- + +- `#6496 <https://github.com/pytest-dev/pytest/issues/6496>`_: Revert `#6436 <https://github.com/pytest-dev/pytest/issues/6436>`__: unfortunately this change has caused a number of regressions in many suites, + so the team decided to revert this change and make a new release while we continue to look for a solution. + + +pytest 5.3.3 (2020-01-16) +========================= + +Bug Fixes +--------- + +- `#2780 <https://github.com/pytest-dev/pytest/issues/2780>`_: Captured output during teardown is shown with ``-rP``. + + +- `#5971 <https://github.com/pytest-dev/pytest/issues/5971>`_: Fix a ``pytest-xdist`` crash when dealing with exceptions raised in subprocesses created by the + ``multiprocessing`` module. + + +- `#6436 <https://github.com/pytest-dev/pytest/issues/6436>`_: :class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and + parameterized fixtures that execute before them in the fixture stack so they are torn + down at the right times, and in the right order. + + +- `#6532 <https://github.com/pytest-dev/pytest/issues/6532>`_: Fix parsing of outcomes containing multiple errors with ``testdir`` results (regression in 5.3.0). + + + +Trivial/Internal Changes +------------------------ + +- `#6350 <https://github.com/pytest-dev/pytest/issues/6350>`_: Optimized automatic renaming of test parameter IDs. + + +pytest 5.3.2 (2019-12-13) +========================= + +Improvements +------------ + +- `#4639 <https://github.com/pytest-dev/pytest/issues/4639>`_: Revert "A warning is now issued when assertions are made for ``None``". + + The warning proved to be less useful than initially expected and had quite a + few false positive cases. + + + +Bug Fixes +--------- + +- `#5430 <https://github.com/pytest-dev/pytest/issues/5430>`_: junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. + + +- `#6290 <https://github.com/pytest-dev/pytest/issues/6290>`_: The supporting files in the ``.pytest_cache`` directory are kept with ``--cache-clear``, which only clears cached values now. + + +- `#6301 <https://github.com/pytest-dev/pytest/issues/6301>`_: Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``). + + +pytest 5.3.1 (2019-11-25) +========================= + +Improvements +------------ + +- `#6231 <https://github.com/pytest-dev/pytest/issues/6231>`_: Improve check for misspelling of :ref:`pytest.mark.parametrize ref`. + + +- `#6257 <https://github.com/pytest-dev/pytest/issues/6257>`_: Handle :py:func:`pytest.exit` being used via :py:func:`~_pytest.hookspec.pytest_internalerror`, e.g. when quitting pdb from post mortem. + + + +Bug Fixes +--------- + +- `#5914 <https://github.com/pytest-dev/pytest/issues/5914>`_: pytester: fix :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` when used after positive matching. + + +- `#6082 <https://github.com/pytest-dev/pytest/issues/6082>`_: Fix line detection for doctest samples inside :py:class:`python:property` docstrings, as a workaround to `bpo-17446 <https://bugs.python.org/issue17446>`__. + + +- `#6254 <https://github.com/pytest-dev/pytest/issues/6254>`_: Fix compatibility with pytest-parallel (regression in pytest 5.3.0). + + +- `#6255 <https://github.com/pytest-dev/pytest/issues/6255>`_: Clear the :py:data:`sys.last_traceback`, :py:data:`sys.last_type` + and :py:data:`sys.last_value` attributes by deleting them instead + of setting them to ``None``. This better matches the behaviour of + the Python standard library. + + +pytest 5.3.0 (2019-11-19) +========================= + +Deprecations +------------ + +- `#6179 <https://github.com/pytest-dev/pytest/issues/6179>`_: The default value of :confval:`junit_family` option will change to ``"xunit2"`` in pytest 6.0, given + that this is the version supported by default in modern tools that manipulate this type of file. + + In order to smooth the transition, pytest will issue a warning in case the ``--junitxml`` option + is given in the command line but :confval:`junit_family` is not explicitly configured in ``pytest.ini``. + + For more information, `see the docs <https://docs.pytest.org/en/stable/deprecations.html#junit-family-default-value-change-to-xunit2>`__. + + + +Features +-------- + +- `#4488 <https://github.com/pytest-dev/pytest/issues/4488>`_: The pytest team has created the `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ + plugin, which provides a new ``--report-log=FILE`` option that writes *report logs* into a file as the test session executes. + + Each line of the report log contains a self contained JSON object corresponding to a testing event, + such as a collection or a test result report. The file is guaranteed to be flushed after writing + each line, so systems can read and process events in real-time. + + The plugin is meant to replace the ``--resultlog`` option, which is deprecated and meant to be removed + in a future release. If you use ``--resultlog``, please try out ``pytest-reportlog`` and + provide feedback. + + +- `#4730 <https://github.com/pytest-dev/pytest/issues/4730>`_: When :py:data:`sys.pycache_prefix` (Python 3.8+) is set, it will be used by pytest to cache test files changed by the assertion rewriting mechanism. + + This makes it easier to benefit of cached ``.pyc`` files even on file systems without permissions. + + +- `#5515 <https://github.com/pytest-dev/pytest/issues/5515>`_: Allow selective auto-indentation of multiline log messages. + + Adds command line option ``--log-auto-indent``, config option + :confval:`log_auto_indent` and support for per-entry configuration of + indentation behavior on calls to :py:func:`python:logging.log()`. + + Alters the default for auto-indention from ``"on"`` to ``"off"``. This + restores the older behavior that existed prior to v4.6.0. This + reversion to earlier behavior was done because it is better to + activate new features that may lead to broken tests explicitly + rather than implicitly. + + +- `#5914 <https://github.com/pytest-dev/pytest/issues/5914>`_: :fixture:`testdir` learned two new functions, :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` and + :py:func:`~_pytest.pytester.LineMatcher.no_re_match_line`. + + The functions are used to ensure the captured text *does not* match the given + pattern. + + The previous idiom was to use :py:func:`python:re.match`: + + .. code-block:: python + + result = testdir.runpytest() + assert re.match(pat, result.stdout.str()) is None + + Or the ``in`` operator: + + .. code-block:: python + + result = testdir.runpytest() + assert text in result.stdout.str() + + But the new functions produce best output on failure. + + +- `#6057 <https://github.com/pytest-dev/pytest/issues/6057>`_: Added tolerances to complex values when printing ``pytest.approx``. + + For example, ``repr(pytest.approx(3+4j))`` returns ``(3+4j) ± 5e-06 ∠ ±180°``. This is polar notation indicating a circle around the expected value, with a radius of 5e-06. For ``approx`` comparisons to return ``True``, the actual value should fall within this circle. + + +- `#6061 <https://github.com/pytest-dev/pytest/issues/6061>`_: Added the pluginmanager as an argument to ``pytest_addoption`` + so that hooks can be invoked when setting up command line options. This is + useful for having one plugin communicate things to another plugin, + such as default values or which set of command line options to add. + + + +Improvements +------------ + +- `#5061 <https://github.com/pytest-dev/pytest/issues/5061>`_: Use multiple colors with terminal summary statistics. + + +- `#5630 <https://github.com/pytest-dev/pytest/issues/5630>`_: Quitting from debuggers is now properly handled in ``doctest`` items. + + +- `#5924 <https://github.com/pytest-dev/pytest/issues/5924>`_: Improved verbose diff output with sequences. + + Before: + + :: + + E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...] + E Right contains 3 more items, first extra item: ' ' + E Full diff: + E - ['version', 'version_info', 'sys.version', 'sys.version_info'] + E + ['version', + E + 'version_info', + E + 'sys.version', + E + 'sys.version_info', + E + ' ', + E + 'sys.version', + E + 'sys.version_info'] + + After: + + :: + + E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...] + E Right contains 3 more items, first extra item: ' ' + E Full diff: + E [ + E 'version', + E 'version_info', + E 'sys.version', + E 'sys.version_info', + E + ' ', + E + 'sys.version', + E + 'sys.version_info', + E ] + + +- `#5934 <https://github.com/pytest-dev/pytest/issues/5934>`_: ``repr`` of ``ExceptionInfo`` objects has been improved to honor the ``__repr__`` method of the underlying exception. + +- `#5936 <https://github.com/pytest-dev/pytest/issues/5936>`_: Display untruncated assertion message with ``-vv``. + + +- `#5990 <https://github.com/pytest-dev/pytest/issues/5990>`_: Fixed plurality mismatch in test summary (e.g. display "1 error" instead of "1 errors"). + + +- `#6008 <https://github.com/pytest-dev/pytest/issues/6008>`_: ``Config.InvocationParams.args`` is now always a ``tuple`` to better convey that it should be + immutable and avoid accidental modifications. + + +- `#6023 <https://github.com/pytest-dev/pytest/issues/6023>`_: ``pytest.main`` returns a ``pytest.ExitCode`` instance now, except for when custom exit codes are used (where it returns ``int`` then still). + + +- `#6026 <https://github.com/pytest-dev/pytest/issues/6026>`_: Align prefixes in output of pytester's ``LineMatcher``. + + +- `#6059 <https://github.com/pytest-dev/pytest/issues/6059>`_: Collection errors are reported as errors (and not failures like before) in the terminal's short test summary. + + +- `#6069 <https://github.com/pytest-dev/pytest/issues/6069>`_: ``pytester.spawn`` does not skip/xfail tests on FreeBSD anymore unconditionally. + + +- `#6097 <https://github.com/pytest-dev/pytest/issues/6097>`_: The "[...%]" indicator in the test summary is now colored according to the final (new) multi-colored line's main color. + + +- `#6116 <https://github.com/pytest-dev/pytest/issues/6116>`_: Added ``--co`` as a synonym to ``--collect-only``. + + +- `#6148 <https://github.com/pytest-dev/pytest/issues/6148>`_: ``atomicwrites`` is now only used on Windows, fixing a performance regression with assertion rewriting on Unix. + + +- `#6152 <https://github.com/pytest-dev/pytest/issues/6152>`_: Now parametrization will use the ``__name__`` attribute of any object for the id, if present. Previously it would only use ``__name__`` for functions and classes. + + +- `#6176 <https://github.com/pytest-dev/pytest/issues/6176>`_: Improved failure reporting with pytester's ``Hookrecorder.assertoutcome``. + + +- `#6181 <https://github.com/pytest-dev/pytest/issues/6181>`_: The reason for a stopped session, e.g. with ``--maxfail`` / ``-x``, now gets reported in the test summary. + + +- `#6206 <https://github.com/pytest-dev/pytest/issues/6206>`_: Improved ``cache.set`` robustness and performance. + + + +Bug Fixes +--------- + +- `#2049 <https://github.com/pytest-dev/pytest/issues/2049>`_: Fixed ``--setup-plan`` showing inaccurate information about fixture lifetimes. + + +- `#2548 <https://github.com/pytest-dev/pytest/issues/2548>`_: Fixed line offset mismatch of skipped tests in terminal summary. + + +- `#6039 <https://github.com/pytest-dev/pytest/issues/6039>`_: The ``PytestDoctestRunner`` is now properly invalidated when unconfiguring the doctest plugin. + + This is important when used with ``pytester``'s ``runpytest_inprocess``. + + +- `#6047 <https://github.com/pytest-dev/pytest/issues/6047>`_: BaseExceptions are now handled in ``saferepr``, which includes ``pytest.fail.Exception`` etc. + + +- `#6074 <https://github.com/pytest-dev/pytest/issues/6074>`_: pytester: fixed order of arguments in ``rm_rf`` warning when cleaning up temporary directories, and do not emit warnings for errors with ``os.open``. + + +- `#6189 <https://github.com/pytest-dev/pytest/issues/6189>`_: Fixed result of ``getmodpath`` method. + + + +Trivial/Internal Changes +------------------------ + +- `#4901 <https://github.com/pytest-dev/pytest/issues/4901>`_: ``RunResult`` from ``pytester`` now displays the mnemonic of the ``ret`` attribute when it is a + valid ``pytest.ExitCode`` value. + + +pytest 5.2.4 (2019-11-15) +========================= + +Bug Fixes +--------- + +- `#6194 <https://github.com/pytest-dev/pytest/issues/6194>`_: Fix incorrect discovery of non-test ``__init__.py`` files. + + +- `#6197 <https://github.com/pytest-dev/pytest/issues/6197>`_: Revert "The first test in a package (``__init__.py``) marked with ``@pytest.mark.skip`` is now correctly skipped.". + + +pytest 5.2.3 (2019-11-14) +========================= + +Bug Fixes +--------- + +- `#5830 <https://github.com/pytest-dev/pytest/issues/5830>`_: The first test in a package (``__init__.py``) marked with ``@pytest.mark.skip`` is now correctly skipped. + + +- `#6099 <https://github.com/pytest-dev/pytest/issues/6099>`_: Fix ``--trace`` when used with parametrized functions. + + +- `#6183 <https://github.com/pytest-dev/pytest/issues/6183>`_: Using ``request`` as a parameter name in ``@pytest.mark.parametrize`` now produces a more + user-friendly error. + + +pytest 5.2.2 (2019-10-24) +========================= + +Bug Fixes +--------- + +- `#5206 <https://github.com/pytest-dev/pytest/issues/5206>`_: Fix ``--nf`` to not forget about known nodeids with partial test selection. + + +- `#5906 <https://github.com/pytest-dev/pytest/issues/5906>`_: Fix crash with ``KeyboardInterrupt`` during ``--setup-show``. + + +- `#5946 <https://github.com/pytest-dev/pytest/issues/5946>`_: Fixed issue when parametrizing fixtures with numpy arrays (and possibly other sequence-like types). + + +- `#6044 <https://github.com/pytest-dev/pytest/issues/6044>`_: Properly ignore ``FileNotFoundError`` exceptions when trying to remove old temporary directories, + for instance when multiple processes try to remove the same directory (common with ``pytest-xdist`` + for example). + + +pytest 5.2.1 (2019-10-06) +========================= + +Bug Fixes +--------- + +- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``. + + +pytest 5.2.0 (2019-09-28) +========================= + +Deprecations +------------ + +- `#1682 <https://github.com/pytest-dev/pytest/issues/1682>`_: Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them + as a keyword argument instead. + + + +Features +-------- + +- `#1682 <https://github.com/pytest-dev/pytest/issues/1682>`_: The ``scope`` parameter of ``@pytest.fixture`` can now be a callable that receives + the fixture name and the ``config`` object as keyword-only parameters. + See `the docs <https://docs.pytest.org/en/stable/fixture.html#dynamic-scope>`__ for more information. + + +- `#5764 <https://github.com/pytest-dev/pytest/issues/5764>`_: New behavior of the ``--pastebin`` option: failures to connect to the pastebin server are reported, without failing the pytest run + + + +Bug Fixes +--------- + +- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text". + + +- `#5884 <https://github.com/pytest-dev/pytest/issues/5884>`_: Fix ``--setup-only`` and ``--setup-show`` for custom pytest items. + + + +Trivial/Internal Changes +------------------------ + +- `#5056 <https://github.com/pytest-dev/pytest/issues/5056>`_: The HelpFormatter uses ``py.io.get_terminal_width`` for better width detection. + + +pytest 5.1.3 (2019-09-18) +========================= + +Bug Fixes +--------- + +- `#5807 <https://github.com/pytest-dev/pytest/issues/5807>`_: Fix pypy3.6 (nightly) on windows. + + +- `#5811 <https://github.com/pytest-dev/pytest/issues/5811>`_: Handle ``--fulltrace`` correctly with ``pytest.raises``. + + +- `#5819 <https://github.com/pytest-dev/pytest/issues/5819>`_: Windows: Fix regression with conftest whose qualified name contains uppercase + characters (introduced by #5792). + + +pytest 5.1.2 (2019-08-30) +========================= + +Bug Fixes +--------- + +- `#2270 <https://github.com/pytest-dev/pytest/issues/2270>`_: Fixed ``self`` reference in function-scoped fixtures defined plugin classes: previously ``self`` + would be a reference to a *test* class, not the *plugin* class. + + +- `#570 <https://github.com/pytest-dev/pytest/issues/570>`_: Fixed long standing issue where fixture scope was not respected when indirect fixtures were used during + parametrization. + + +- `#5782 <https://github.com/pytest-dev/pytest/issues/5782>`_: Fix decoding error when printing an error response from ``--pastebin``. + + +- `#5786 <https://github.com/pytest-dev/pytest/issues/5786>`_: Chained exceptions in test and collection reports are now correctly serialized, allowing plugins like + ``pytest-xdist`` to display them properly. + + +- `#5792 <https://github.com/pytest-dev/pytest/issues/5792>`_: Windows: Fix error that occurs in certain circumstances when loading + ``conftest.py`` from a working directory that has casing other than the one stored + in the filesystem (e.g., ``c:\test`` instead of ``C:\test``). + + +pytest 5.1.1 (2019-08-20) +========================= + +Bug Fixes +--------- + +- `#5751 <https://github.com/pytest-dev/pytest/issues/5751>`_: Fixed ``TypeError`` when importing pytest on Python 3.5.0 and 3.5.1. + + +pytest 5.1.0 (2019-08-15) +========================= + +Removals +-------- + +- `#5180 <https://github.com/pytest-dev/pytest/issues/5180>`_: As per our policy, the following features have been deprecated in the 4.X series and are now + removed: + + * ``Request.getfuncargvalue``: use ``Request.getfixturevalue`` instead. + + * ``pytest.raises`` and ``pytest.warns`` no longer support strings as the second argument. + + * ``message`` parameter of ``pytest.raises``. + + * ``pytest.raises``, ``pytest.warns`` and ``ParameterSet.param`` now use native keyword-only + syntax. This might change the exception message from previous versions, but they still raise + ``TypeError`` on unknown keyword arguments as before. + + * ``pytest.config`` global variable. + + * ``tmpdir_factory.ensuretemp`` method. + + * ``pytest_logwarning`` hook. + + * ``RemovedInPytest4Warning`` warning type. + + * ``request`` is now a reserved name for fixtures. + + + For more information consult + `Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`__ in the docs. + + +- `#5565 <https://github.com/pytest-dev/pytest/issues/5565>`_: Removed unused support code for `unittest2 <https://pypi.org/project/unittest2/>`__. + + The ``unittest2`` backport module is no longer + necessary since Python 3.3+, and the small amount of code in pytest to support it also doesn't seem + to be used: after removed, all tests still pass unchanged. + + Although our policy is to introduce a deprecation period before removing any features or support + for third party libraries, because this code is apparently not used + at all (even if ``unittest2`` is used by a test suite executed by pytest), it was decided to + remove it in this release. + + If you experience a regression because of this, please + `file an issue <https://github.com/pytest-dev/pytest/issues/new>`__. + + +- `#5615 <https://github.com/pytest-dev/pytest/issues/5615>`_: ``pytest.fail``, ``pytest.xfail`` and ``pytest.skip`` no longer support bytes for the message argument. + + This was supported for Python 2 where it was tempting to use ``"message"`` + instead of ``u"message"``. + + Python 3 code is unlikely to pass ``bytes`` to these functions. If you do, + please decode it to an ``str`` beforehand. + + + +Features +-------- + +- `#5564 <https://github.com/pytest-dev/pytest/issues/5564>`_: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``. + + +- `#5576 <https://github.com/pytest-dev/pytest/issues/5576>`_: New `NUMBER <https://docs.pytest.org/en/stable/doctest.html#using-doctest-options>`__ + option for doctests to ignore irrelevant differences in floating-point numbers. + Inspired by Sébastien Boisgérault's `numtest <https://github.com/boisgera/numtest>`__ + extension for doctest. + + + +Improvements +------------ + +- `#5471 <https://github.com/pytest-dev/pytest/issues/5471>`_: JUnit XML now includes a timestamp and hostname in the testsuite tag. + + +- `#5707 <https://github.com/pytest-dev/pytest/issues/5707>`_: Time taken to run the test suite now includes a human-readable representation when it takes over + 60 seconds, for example:: + + ===== 2 failed in 102.70s (0:01:42) ===== + + + +Bug Fixes +--------- + +- `#4344 <https://github.com/pytest-dev/pytest/issues/4344>`_: Fix RuntimeError/StopIteration when trying to collect package with "__init__.py" only. + + +- `#5115 <https://github.com/pytest-dev/pytest/issues/5115>`_: Warnings issued during ``pytest_configure`` are explicitly not treated as errors, even if configured as such, because it otherwise completely breaks pytest. + + +- `#5477 <https://github.com/pytest-dev/pytest/issues/5477>`_: The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element. + + +- `#5524 <https://github.com/pytest-dev/pytest/issues/5524>`_: Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only, + which could lead to pytest crashing when executed a second time with the ``--basetemp`` option. + + +- `#5537 <https://github.com/pytest-dev/pytest/issues/5537>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the + standard library on Python 3.8+. + + +- `#5578 <https://github.com/pytest-dev/pytest/issues/5578>`_: Improve type checking for some exception-raising functions (``pytest.xfail``, ``pytest.skip``, etc) + so they provide better error messages when users meant to use marks (for example ``@pytest.xfail`` + instead of ``@pytest.mark.xfail``). + + +- `#5606 <https://github.com/pytest-dev/pytest/issues/5606>`_: Fixed internal error when test functions were patched with objects that cannot be compared + for truth values against others, like ``numpy`` arrays. + + +- `#5634 <https://github.com/pytest-dev/pytest/issues/5634>`_: ``pytest.exit`` is now correctly handled in ``unittest`` cases. + This makes ``unittest`` cases handle ``quit`` from pytest's pdb correctly. + + +- `#5650 <https://github.com/pytest-dev/pytest/issues/5650>`_: Improved output when parsing an ini configuration file fails. + + +- `#5701 <https://github.com/pytest-dev/pytest/issues/5701>`_: Fix collection of ``staticmethod`` objects defined with ``functools.partial``. + + +- `#5734 <https://github.com/pytest-dev/pytest/issues/5734>`_: Skip async generator test functions, and update the warning message to refer to ``async def`` functions. + + + +Improved Documentation +---------------------- + +- `#5669 <https://github.com/pytest-dev/pytest/issues/5669>`_: Add docstring for ``Testdir.copy_example``. + + + +Trivial/Internal Changes +------------------------ + +- `#5095 <https://github.com/pytest-dev/pytest/issues/5095>`_: XML files of the ``xunit2`` family are now validated against the schema by pytest's own test suite + to avoid future regressions. + + +- `#5516 <https://github.com/pytest-dev/pytest/issues/5516>`_: Cache node splitting function which can improve collection performance in very large test suites. + + +- `#5603 <https://github.com/pytest-dev/pytest/issues/5603>`_: Simplified internal ``SafeRepr`` class and removed some dead code. + + +- `#5664 <https://github.com/pytest-dev/pytest/issues/5664>`_: When invoking pytest's own testsuite with ``PYTHONDONTWRITEBYTECODE=1``, + the ``test_xfail_handling`` test no longer fails. + + +- `#5684 <https://github.com/pytest-dev/pytest/issues/5684>`_: Replace manual handling of ``OSError.errno`` in the codebase by new ``OSError`` subclasses (``PermissionError``, ``FileNotFoundError``, etc.). + + +pytest 5.0.1 (2019-07-04) +========================= + +Bug Fixes +--------- + +- `#5479 <https://github.com/pytest-dev/pytest/issues/5479>`_: Improve quoting in ``raises`` match failure message. + + +- `#5523 <https://github.com/pytest-dev/pytest/issues/5523>`_: Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+. + + +- `#5547 <https://github.com/pytest-dev/pytest/issues/5547>`_: ``--step-wise`` now handles ``xfail(strict=True)`` markers properly. + + + +Improved Documentation +---------------------- + +- `#5517 <https://github.com/pytest-dev/pytest/issues/5517>`_: Improve "Declaring new hooks" section in chapter "Writing Plugins" + + +pytest 5.0.0 (2019-06-28) +========================= + +Important +--------- + +This release is a Python3.5+ only release. + +For more details, see our `Python 2.7 and 3.4 support plan <https://docs.pytest.org/en/stable/py27-py34-deprecation.html>`__. + +Removals +-------- + +- `#1149 <https://github.com/pytest-dev/pytest/issues/1149>`_: Pytest no longer accepts prefixes of command-line arguments, for example + typing ``pytest --doctest-mod`` inplace of ``--doctest-modules``. + This was previously allowed where the ``ArgumentParser`` thought it was unambiguous, + but this could be incorrect due to delayed parsing of options for plugins. + See for example issues `#1149 <https://github.com/pytest-dev/pytest/issues/1149>`__, + `#3413 <https://github.com/pytest-dev/pytest/issues/3413>`__, and + `#4009 <https://github.com/pytest-dev/pytest/issues/4009>`__. + + +- `#5402 <https://github.com/pytest-dev/pytest/issues/5402>`_: **PytestDeprecationWarning are now errors by default.** + + Following our plan to remove deprecated features with as little disruption as + possible, all warnings of type ``PytestDeprecationWarning`` now generate errors + instead of warning messages. + + **The affected features will be effectively removed in pytest 5.1**, so please consult the + `Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`__ + section in the docs for directions on how to update existing code. + + In the pytest ``5.0.X`` series, it is possible to change the errors back into warnings as a stop + gap measure by adding this to your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + filterwarnings = + ignore::pytest.PytestDeprecationWarning + + But this will stop working when pytest ``5.1`` is released. + + **If you have concerns** about the removal of a specific feature, please add a + comment to `#5402 <https://github.com/pytest-dev/pytest/issues/5402>`__. + + +- `#5412 <https://github.com/pytest-dev/pytest/issues/5412>`_: ``ExceptionInfo`` objects (returned by ``pytest.raises``) now have the same ``str`` representation as ``repr``, which + avoids some confusion when users use ``print(e)`` to inspect the object. + + This means code like: + + .. code-block:: python + + with pytest.raises(SomeException) as e: + ... + assert "some message" in str(e) + + + Needs to be changed to: + + .. code-block:: python + + with pytest.raises(SomeException) as e: + ... + assert "some message" in str(e.value) + + + + +Deprecations +------------ + +- `#4488 <https://github.com/pytest-dev/pytest/issues/4488>`_: The removal of the ``--result-log`` option and module has been postponed to (tentatively) pytest 6.0 as + the team has not yet got around to implement a good alternative for it. + + +- `#466 <https://github.com/pytest-dev/pytest/issues/466>`_: The ``funcargnames`` attribute has been an alias for ``fixturenames`` since + pytest 2.3, and is now deprecated in code too. + + + +Features +-------- + +- `#3457 <https://github.com/pytest-dev/pytest/issues/3457>`_: New `pytest_assertion_pass <https://docs.pytest.org/en/stable/reference.html#_pytest.hookspec.pytest_assertion_pass>`__ + hook, called with context information when an assertion *passes*. + + This hook is still **experimental** so use it with caution. + + +- `#5440 <https://github.com/pytest-dev/pytest/issues/5440>`_: The `faulthandler <https://docs.python.org/3/library/faulthandler.html>`__ standard library + module is now enabled by default to help users diagnose crashes in C modules. + + This functionality was provided by integrating the external + `pytest-faulthandler <https://github.com/pytest-dev/pytest-faulthandler>`__ plugin into the core, + so users should remove that plugin from their requirements if used. + + For more information see the docs: https://docs.pytest.org/en/stable/usage.html#fault-handler + + +- `#5452 <https://github.com/pytest-dev/pytest/issues/5452>`_: When warnings are configured as errors, pytest warnings now appear as originating from ``pytest.`` instead of the internal ``_pytest.warning_types.`` module. + + +- `#5125 <https://github.com/pytest-dev/pytest/issues/5125>`_: ``Session.exitcode`` values are now coded in ``pytest.ExitCode``, an ``IntEnum``. This makes the exit code available for consumer code and are more explicit other than just documentation. User defined exit codes are still valid, but should be used with caution. + + The team doesn't expect this change to break test suites or plugins in general, except in esoteric/specific scenarios. + + **pytest-xdist** users should upgrade to ``1.29.0`` or later, as ``pytest-xdist`` required a compatibility fix because of this change. + + + +Bug Fixes +--------- + +- `#1403 <https://github.com/pytest-dev/pytest/issues/1403>`_: Switch from ``imp`` to ``importlib``. + + +- `#1671 <https://github.com/pytest-dev/pytest/issues/1671>`_: The name of the ``.pyc`` files cached by the assertion writer now includes the pytest version + to avoid stale caches. + + +- `#2761 <https://github.com/pytest-dev/pytest/issues/2761>`_: Honor PEP 235 on case-insensitive file systems. + + +- `#5078 <https://github.com/pytest-dev/pytest/issues/5078>`_: Test module is no longer double-imported when using ``--pyargs``. + + +- `#5260 <https://github.com/pytest-dev/pytest/issues/5260>`_: Improved comparison of byte strings. + + When comparing bytes, the assertion message used to show the byte numeric value when showing the differences:: + + def test(): + > assert b'spam' == b'eggs' + E AssertionError: assert b'spam' == b'eggs' + E At index 0 diff: 115 != 101 + E Use -v to get the full diff + + It now shows the actual ascii representation instead, which is often more useful:: + + def test(): + > assert b'spam' == b'eggs' + E AssertionError: assert b'spam' == b'eggs' + E At index 0 diff: b's' != b'e' + E Use -v to get the full diff + + +- `#5335 <https://github.com/pytest-dev/pytest/issues/5335>`_: Colorize level names when the level in the logging format is formatted using + '%(levelname).Xs' (truncated fixed width alignment), where X is an integer. + + +- `#5354 <https://github.com/pytest-dev/pytest/issues/5354>`_: Fix ``pytest.mark.parametrize`` when the argvalues is an iterator. + + +- `#5370 <https://github.com/pytest-dev/pytest/issues/5370>`_: Revert unrolling of ``all()`` to fix ``NameError`` on nested comprehensions. + + +- `#5371 <https://github.com/pytest-dev/pytest/issues/5371>`_: Revert unrolling of ``all()`` to fix incorrect handling of generators with ``if``. + + +- `#5372 <https://github.com/pytest-dev/pytest/issues/5372>`_: Revert unrolling of ``all()`` to fix incorrect assertion when using ``all()`` in an expression. + + +- `#5383 <https://github.com/pytest-dev/pytest/issues/5383>`_: ``-q`` has again an impact on the style of the collected items + (``--collect-only``) when ``--log-cli-level`` is used. + + +- `#5389 <https://github.com/pytest-dev/pytest/issues/5389>`_: Fix regressions of `#5063 <https://github.com/pytest-dev/pytest/pull/5063>`__ for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``. + + +- `#5390 <https://github.com/pytest-dev/pytest/issues/5390>`_: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods. + + +- `#5404 <https://github.com/pytest-dev/pytest/issues/5404>`_: Emit a warning when attempting to unwrap a broken object raises an exception, + for easier debugging (`#5080 <https://github.com/pytest-dev/pytest/issues/5080>`__). + + +- `#5432 <https://github.com/pytest-dev/pytest/issues/5432>`_: Prevent "already imported" warnings from assertion rewriter when invoking pytest in-process multiple times. + + +- `#5433 <https://github.com/pytest-dev/pytest/issues/5433>`_: Fix assertion rewriting in packages (``__init__.py``). + + +- `#5444 <https://github.com/pytest-dev/pytest/issues/5444>`_: Fix ``--stepwise`` mode when the first file passed on the command-line fails to collect. + + +- `#5482 <https://github.com/pytest-dev/pytest/issues/5482>`_: Fix bug introduced in 4.6.0 causing collection errors when passing + more than 2 positional arguments to ``pytest.mark.parametrize``. + + +- `#5505 <https://github.com/pytest-dev/pytest/issues/5505>`_: Fix crash when discovery fails while using ``-p no:terminal``. + + + +Improved Documentation +---------------------- + +- `#5315 <https://github.com/pytest-dev/pytest/issues/5315>`_: Expand docs on mocking classes and dictionaries with ``monkeypatch``. + + +- `#5416 <https://github.com/pytest-dev/pytest/issues/5416>`_: Fix PytestUnknownMarkWarning in run/skip example. + + +pytest 4.6.9 (2020-01-04) +========================= + +Bug Fixes +--------- + +- `#6301 <https://github.com/pytest-dev/pytest/issues/6301>`_: Fix assertion rewriting for egg-based distributions and ``editable`` installs (``pip install --editable``). + + +pytest 4.6.8 (2019-12-19) +========================= + +Features +-------- + +- `#5471 <https://github.com/pytest-dev/pytest/issues/5471>`_: JUnit XML now includes a timestamp and hostname in the testsuite tag. + + + +Bug Fixes +--------- + +- `#5430 <https://github.com/pytest-dev/pytest/issues/5430>`_: junitxml: Logs for failed test are now passed to junit report in case the test fails during call phase. + + + +Trivial/Internal Changes +------------------------ + +- `#6345 <https://github.com/pytest-dev/pytest/issues/6345>`_: Pin ``colorama`` to ``0.4.1`` only for Python 3.4 so newer Python versions can still receive colorama updates. + + +pytest 4.6.7 (2019-12-05) +========================= + +Bug Fixes +--------- + +- `#5477 <https://github.com/pytest-dev/pytest/issues/5477>`_: The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element. + + +- `#6044 <https://github.com/pytest-dev/pytest/issues/6044>`_: Properly ignore ``FileNotFoundError`` (``OSError.errno == NOENT`` in Python 2) exceptions when trying to remove old temporary directories, + for instance when multiple processes try to remove the same directory (common with ``pytest-xdist`` + for example). + + +pytest 4.6.6 (2019-10-11) +========================= + +Bug Fixes +--------- + +- `#5523 <https://github.com/pytest-dev/pytest/issues/5523>`_: Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+. + + +- `#5537 <https://github.com/pytest-dev/pytest/issues/5537>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the + standard library on Python 3.8+. + + +- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text". + + +- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``. + + + +Trivial/Internal Changes +------------------------ + +- `#5801 <https://github.com/pytest-dev/pytest/issues/5801>`_: Fixes python version checks (detected by ``flake8-2020``) in case python4 becomes a thing. + + +pytest 4.6.5 (2019-08-05) +========================= + +Bug Fixes +--------- + +- `#4344 <https://github.com/pytest-dev/pytest/issues/4344>`_: Fix RuntimeError/StopIteration when trying to collect package with "__init__.py" only. + + +- `#5478 <https://github.com/pytest-dev/pytest/issues/5478>`_: Fix encode error when using unicode strings in exceptions with ``pytest.raises``. + + +- `#5524 <https://github.com/pytest-dev/pytest/issues/5524>`_: Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only, + which could lead to pytest crashing when executed a second time with the ``--basetemp`` option. + + +- `#5547 <https://github.com/pytest-dev/pytest/issues/5547>`_: ``--step-wise`` now handles ``xfail(strict=True)`` markers properly. + + +- `#5650 <https://github.com/pytest-dev/pytest/issues/5650>`_: Improved output when parsing an ini configuration file fails. + +pytest 4.6.4 (2019-06-28) +========================= + +Bug Fixes +--------- + +- `#5404 <https://github.com/pytest-dev/pytest/issues/5404>`_: Emit a warning when attempting to unwrap a broken object raises an exception, + for easier debugging (`#5080 <https://github.com/pytest-dev/pytest/issues/5080>`__). + + +- `#5444 <https://github.com/pytest-dev/pytest/issues/5444>`_: Fix ``--stepwise`` mode when the first file passed on the command-line fails to collect. + + +- `#5482 <https://github.com/pytest-dev/pytest/issues/5482>`_: Fix bug introduced in 4.6.0 causing collection errors when passing + more than 2 positional arguments to ``pytest.mark.parametrize``. + + +- `#5505 <https://github.com/pytest-dev/pytest/issues/5505>`_: Fix crash when discovery fails while using ``-p no:terminal``. + + +pytest 4.6.3 (2019-06-11) +========================= + +Bug Fixes +--------- + +- `#5383 <https://github.com/pytest-dev/pytest/issues/5383>`_: ``-q`` has again an impact on the style of the collected items + (``--collect-only``) when ``--log-cli-level`` is used. + + +- `#5389 <https://github.com/pytest-dev/pytest/issues/5389>`_: Fix regressions of `#5063 <https://github.com/pytest-dev/pytest/pull/5063>`__ for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``. + + +- `#5390 <https://github.com/pytest-dev/pytest/issues/5390>`_: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods. + + +pytest 4.6.2 (2019-06-03) +========================= + +Bug Fixes +--------- + +- `#5370 <https://github.com/pytest-dev/pytest/issues/5370>`_: Revert unrolling of ``all()`` to fix ``NameError`` on nested comprehensions. + + +- `#5371 <https://github.com/pytest-dev/pytest/issues/5371>`_: Revert unrolling of ``all()`` to fix incorrect handling of generators with ``if``. + + +- `#5372 <https://github.com/pytest-dev/pytest/issues/5372>`_: Revert unrolling of ``all()`` to fix incorrect assertion when using ``all()`` in an expression. + + +pytest 4.6.1 (2019-06-02) +========================= + +Bug Fixes +--------- + +- `#5354 <https://github.com/pytest-dev/pytest/issues/5354>`_: Fix ``pytest.mark.parametrize`` when the argvalues is an iterator. + + +- `#5358 <https://github.com/pytest-dev/pytest/issues/5358>`_: Fix assertion rewriting of ``all()`` calls to deal with non-generators. + + +pytest 4.6.0 (2019-05-31) +========================= + +Important +--------- + +The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**. + +For more details, see our `Python 2.7 and 3.4 support plan <https://docs.pytest.org/en/stable/py27-py34-deprecation.html>`__. + + +Features +-------- + +- `#4559 <https://github.com/pytest-dev/pytest/issues/4559>`_: Added the ``junit_log_passing_tests`` ini value which can be used to enable or disable logging of passing test output in the Junit XML file. + + +- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: pytester's ``testdir.spawn`` uses ``tmpdir`` as HOME/USERPROFILE directory. + + +- `#5062 <https://github.com/pytest-dev/pytest/issues/5062>`_: Unroll calls to ``all`` to full for-loops with assertion rewriting for better failure messages, especially when using Generator Expressions. + + +- `#5063 <https://github.com/pytest-dev/pytest/issues/5063>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time. + + +- `#5091 <https://github.com/pytest-dev/pytest/issues/5091>`_: The output for ini options in ``--help`` has been improved. + + +- `#5269 <https://github.com/pytest-dev/pytest/issues/5269>`_: ``pytest.importorskip`` includes the ``ImportError`` now in the default ``reason``. + + +- `#5311 <https://github.com/pytest-dev/pytest/issues/5311>`_: Captured logs that are output for each failing test are formatted using the + ColoredLevelFormatter. + + +- `#5312 <https://github.com/pytest-dev/pytest/issues/5312>`_: Improved formatting of multiline log messages in Python 3. + + + +Bug Fixes +--------- + +- `#2064 <https://github.com/pytest-dev/pytest/issues/2064>`_: The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now. + + +- `#4908 <https://github.com/pytest-dev/pytest/issues/4908>`_: The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``). + + +- `#5036 <https://github.com/pytest-dev/pytest/issues/5036>`_: Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized. + + +- `#5256 <https://github.com/pytest-dev/pytest/issues/5256>`_: Handle internal error due to a lone surrogate unicode character not being representable in Jython. + + +- `#5257 <https://github.com/pytest-dev/pytest/issues/5257>`_: Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream. + + +- `#5278 <https://github.com/pytest-dev/pytest/issues/5278>`_: Pytest's internal python plugin can be disabled using ``-p no:python`` again. + + +- `#5286 <https://github.com/pytest-dev/pytest/issues/5286>`_: Fix issue with ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option not working when using a list of test IDs in parametrized tests. + + +- `#5330 <https://github.com/pytest-dev/pytest/issues/5330>`_: Show the test module being collected when emitting ``PytestCollectionWarning`` messages for + test classes with ``__init__`` and ``__new__`` methods to make it easier to pin down the problem. + + +- `#5333 <https://github.com/pytest-dev/pytest/issues/5333>`_: Fix regression in 4.5.0 with ``--lf`` not re-running all tests with known failures from non-selected tests. + + + +Improved Documentation +---------------------- + +- `#5250 <https://github.com/pytest-dev/pytest/issues/5250>`_: Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``. + + +pytest 4.5.0 (2019-05-11) +========================= + +Features +-------- + +- `#4826 <https://github.com/pytest-dev/pytest/issues/4826>`_: A warning is now emitted when unknown marks are used as a decorator. + This is often due to a typo, which can lead to silently broken tests. + + +- `#4907 <https://github.com/pytest-dev/pytest/issues/4907>`_: Show XFail reason as part of JUnitXML message field. + + +- `#5013 <https://github.com/pytest-dev/pytest/issues/5013>`_: Messages from crash reports are displayed within test summaries now, truncated to the terminal width. + + +- `#5023 <https://github.com/pytest-dev/pytest/issues/5023>`_: New flag ``--strict-markers`` that triggers an error when unknown markers (e.g. those not registered using the `markers option`_ in the configuration file) are used in the test suite. + + The existing ``--strict`` option has the same behavior currently, but can be augmented in the future for additional checks. + + .. _`markers option`: https://docs.pytest.org/en/stable/reference.html#confval-markers + + +- `#5026 <https://github.com/pytest-dev/pytest/issues/5026>`_: Assertion failure messages for sequences and dicts contain the number of different items now. + + +- `#5034 <https://github.com/pytest-dev/pytest/issues/5034>`_: Improve reporting with ``--lf`` and ``--ff`` (run-last-failure). + + +- `#5035 <https://github.com/pytest-dev/pytest/issues/5035>`_: The ``--cache-show`` option/action accepts an optional glob to show only matching cache entries. + + +- `#5059 <https://github.com/pytest-dev/pytest/issues/5059>`_: Standard input (stdin) can be given to pytester's ``Testdir.run()`` and ``Testdir.popen()``. + + +- `#5068 <https://github.com/pytest-dev/pytest/issues/5068>`_: The ``-r`` option learnt about ``A`` to display all reports (including passed ones) in the short test summary. + + +- `#5108 <https://github.com/pytest-dev/pytest/issues/5108>`_: The short test summary is displayed after passes with output (``-rP``). + + +- `#5172 <https://github.com/pytest-dev/pytest/issues/5172>`_: The ``--last-failed`` (``--lf``) option got smarter and will now skip entire files if all tests + of that test file have passed in previous runs, greatly speeding up collection. + + +- `#5177 <https://github.com/pytest-dev/pytest/issues/5177>`_: Introduce new specific warning ``PytestWarning`` subclasses to make it easier to filter warnings based on the class, rather than on the message. The new subclasses are: + + + * ``PytestAssertRewriteWarning`` + + * ``PytestCacheWarning`` + + * ``PytestCollectionWarning`` + + * ``PytestConfigWarning`` + + * ``PytestUnhandledCoroutineWarning`` + + * ``PytestUnknownMarkWarning`` + + +- `#5202 <https://github.com/pytest-dev/pytest/issues/5202>`_: New ``record_testsuite_property`` session-scoped fixture allows users to log ``<property>`` tags at the ``testsuite`` + level with the ``junitxml`` plugin. + + The generated XML is compatible with the latest xunit standard, contrary to + the properties recorded by ``record_property`` and ``record_xml_attribute``. + + +- `#5214 <https://github.com/pytest-dev/pytest/issues/5214>`_: The default logging format has been changed to improve readability. Here is an + example of a previous logging message:: + + test_log_cli_enabled_disabled.py 3 CRITICAL critical message logged by test + + This has now become:: + + CRITICAL root:test_log_cli_enabled_disabled.py:3 critical message logged by test + + The formatting can be changed through the `log_format <https://docs.pytest.org/en/stable/reference.html#confval-log_format>`__ configuration option. + + +- `#5220 <https://github.com/pytest-dev/pytest/issues/5220>`_: ``--fixtures`` now also shows fixture scope for scopes other than ``"function"``. + + + +Bug Fixes +--------- + +- `#5113 <https://github.com/pytest-dev/pytest/issues/5113>`_: Deselected items from plugins using ``pytest_collect_modifyitems`` as a hookwrapper are correctly reported now. + + +- `#5144 <https://github.com/pytest-dev/pytest/issues/5144>`_: With usage errors ``exitstatus`` is set to ``EXIT_USAGEERROR`` in the ``pytest_sessionfinish`` hook now as expected. + + +- `#5235 <https://github.com/pytest-dev/pytest/issues/5235>`_: ``outcome.exit`` is not used with ``EOF`` in the pdb wrapper anymore, but only with ``quit``. + + + +Improved Documentation +---------------------- + +- `#4935 <https://github.com/pytest-dev/pytest/issues/4935>`_: Expand docs on registering marks and the effect of ``--strict``. + + + +Trivial/Internal Changes +------------------------ + +- `#4942 <https://github.com/pytest-dev/pytest/issues/4942>`_: ``logging.raiseExceptions`` is not set to ``False`` anymore. + + +- `#5013 <https://github.com/pytest-dev/pytest/issues/5013>`_: pytest now depends on `wcwidth <https://pypi.org/project/wcwidth>`__ to properly track unicode character sizes for more precise terminal output. + + +- `#5059 <https://github.com/pytest-dev/pytest/issues/5059>`_: pytester's ``Testdir.popen()`` uses ``stdout`` and ``stderr`` via keyword arguments with defaults now (``subprocess.PIPE``). + + +- `#5069 <https://github.com/pytest-dev/pytest/issues/5069>`_: The code for the short test summary in the terminal was moved to the terminal plugin. + + +- `#5082 <https://github.com/pytest-dev/pytest/issues/5082>`_: Improved validation of kwargs for various methods in the pytester plugin. + + +- `#5202 <https://github.com/pytest-dev/pytest/issues/5202>`_: ``record_property`` now emits a ``PytestWarning`` when used with ``junit_family=xunit2``: the fixture generates + ``property`` tags as children of ``testcase``, which is not permitted according to the most + `recent schema <https://github.com/jenkinsci/xunit-plugin/blob/master/ + src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd>`__. + + +- `#5239 <https://github.com/pytest-dev/pytest/issues/5239>`_: Pin ``pluggy`` to ``< 1.0`` so we don't update to ``1.0`` automatically when + it gets released: there are planned breaking changes, and we want to ensure + pytest properly supports ``pluggy 1.0``. + + +pytest 4.4.2 (2019-05-08) +========================= + +Bug Fixes +--------- + +- `#5089 <https://github.com/pytest-dev/pytest/issues/5089>`_: Fix crash caused by error in ``__repr__`` function with both ``showlocals`` and verbose output enabled. + + +- `#5139 <https://github.com/pytest-dev/pytest/issues/5139>`_: Eliminate core dependency on 'terminal' plugin. + + +- `#5229 <https://github.com/pytest-dev/pytest/issues/5229>`_: Require ``pluggy>=0.11.0`` which reverts a dependency to ``importlib-metadata`` added in ``0.10.0``. + The ``importlib-metadata`` package cannot be imported when installed as an egg and causes issues when relying on ``setup.py`` to install test dependencies. + + + +Improved Documentation +---------------------- + +- `#5171 <https://github.com/pytest-dev/pytest/issues/5171>`_: Doc: ``pytest_ignore_collect``, ``pytest_collect_directory``, ``pytest_collect_file`` and ``pytest_pycollect_makemodule`` hooks's 'path' parameter documented type is now ``py.path.local`` + + +- `#5188 <https://github.com/pytest-dev/pytest/issues/5188>`_: Improve help for ``--runxfail`` flag. + + + +Trivial/Internal Changes +------------------------ + +- `#5182 <https://github.com/pytest-dev/pytest/issues/5182>`_: Removed internal and unused ``_pytest.deprecated.MARK_INFO_ATTRIBUTE``. + + +pytest 4.4.1 (2019-04-15) +========================= + +Bug Fixes +--------- + +- `#5031 <https://github.com/pytest-dev/pytest/issues/5031>`_: Environment variables are properly restored when using pytester's ``testdir`` fixture. + + +- `#5039 <https://github.com/pytest-dev/pytest/issues/5039>`_: Fix regression with ``--pdbcls``, which stopped working with local modules in 4.0.0. + + +- `#5092 <https://github.com/pytest-dev/pytest/issues/5092>`_: Produce a warning when unknown keywords are passed to ``pytest.param(...)``. + + +- `#5098 <https://github.com/pytest-dev/pytest/issues/5098>`_: Invalidate import caches with ``monkeypatch.syspath_prepend``, which is required with namespace packages being used. + + +pytest 4.4.0 (2019-03-29) +========================= + +Features +-------- + +- `#2224 <https://github.com/pytest-dev/pytest/issues/2224>`_: ``async`` test functions are skipped and a warning is emitted when a suitable + async plugin is not installed (such as ``pytest-asyncio`` or ``pytest-trio``). + + Previously ``async`` functions would not execute at all but still be marked as "passed". + + +- `#2482 <https://github.com/pytest-dev/pytest/issues/2482>`_: Include new ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option to disable ascii-escaping in parametrized values. This may cause a series of problems and as the name makes clear, use at your own risk. + + +- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: The ``-p`` option can now be used to early-load plugins also by entry-point name, instead of just + by module name. + + This makes it possible to early load external plugins like ``pytest-cov`` in the command-line:: + + pytest -p pytest_cov + + +- `#4855 <https://github.com/pytest-dev/pytest/issues/4855>`_: The ``--pdbcls`` option handles classes via module attributes now (e.g. + ``pdb:pdb.Pdb`` with `pdb++`_), and its validation was improved. + + .. _pdb++: https://pypi.org/project/pdbpp/ + + +- `#4875 <https://github.com/pytest-dev/pytest/issues/4875>`_: The `testpaths <https://docs.pytest.org/en/stable/reference.html#confval-testpaths>`__ configuration option is now displayed next + to the ``rootdir`` and ``inifile`` lines in the pytest header if the option is in effect, i.e., directories or file names were + not explicitly passed in the command line. + + Also, ``inifile`` is only displayed if there's a configuration file, instead of an empty ``inifile:`` string. + + +- `#4911 <https://github.com/pytest-dev/pytest/issues/4911>`_: Doctests can be skipped now dynamically using ``pytest.skip()``. + + +- `#4920 <https://github.com/pytest-dev/pytest/issues/4920>`_: Internal refactorings have been made in order to make the implementation of the + `pytest-subtests <https://github.com/pytest-dev/pytest-subtests>`__ plugin + possible, which adds unittest sub-test support and a new ``subtests`` fixture as discussed in + `#1367 <https://github.com/pytest-dev/pytest/issues/1367>`__. + + For details on the internal refactorings, please see the details on the related PR. + + +- `#4931 <https://github.com/pytest-dev/pytest/issues/4931>`_: pytester's ``LineMatcher`` asserts that the passed lines are a sequence. + + +- `#4936 <https://github.com/pytest-dev/pytest/issues/4936>`_: Handle ``-p plug`` after ``-p no:plug``. + + This can be used to override a blocked plugin (e.g. in "addopts") from the + command line etc. + + +- `#4951 <https://github.com/pytest-dev/pytest/issues/4951>`_: Output capturing is handled correctly when only capturing via fixtures (capsys, capfs) with ``pdb.set_trace()``. + + +- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: ``pytester`` sets ``$HOME`` and ``$USERPROFILE`` to the temporary directory during test runs. + + This ensures to not load configuration files from the real user's home directory. + + +- `#4980 <https://github.com/pytest-dev/pytest/issues/4980>`_: Namespace packages are handled better with ``monkeypatch.syspath_prepend`` and ``testdir.syspathinsert`` (via ``pkg_resources.fixup_namespace_packages``). + + +- `#4993 <https://github.com/pytest-dev/pytest/issues/4993>`_: The stepwise plugin reports status information now. + + +- `#5008 <https://github.com/pytest-dev/pytest/issues/5008>`_: If a ``setup.cfg`` file contains ``[tool:pytest]`` and also the no longer supported ``[pytest]`` section, pytest will use ``[tool:pytest]`` ignoring ``[pytest]``. Previously it would unconditionally error out. + + This makes it simpler for plugins to support old pytest versions. + + + +Bug Fixes +--------- + +- `#1895 <https://github.com/pytest-dev/pytest/issues/1895>`_: Fix bug where fixtures requested dynamically via ``request.getfixturevalue()`` might be teardown + before the requesting fixture. + + +- `#4851 <https://github.com/pytest-dev/pytest/issues/4851>`_: pytester unsets ``PYTEST_ADDOPTS`` now to not use outer options with ``testdir.runpytest()``. + + +- `#4903 <https://github.com/pytest-dev/pytest/issues/4903>`_: Use the correct modified time for years after 2038 in rewritten ``.pyc`` files. + + +- `#4928 <https://github.com/pytest-dev/pytest/issues/4928>`_: Fix line offsets with ``ScopeMismatch`` errors. + + +- `#4957 <https://github.com/pytest-dev/pytest/issues/4957>`_: ``-p no:plugin`` is handled correctly for default (internal) plugins now, e.g. with ``-p no:capture``. + + Previously they were loaded (imported) always, making e.g. the ``capfd`` fixture available. + + +- `#4968 <https://github.com/pytest-dev/pytest/issues/4968>`_: The pdb ``quit`` command is handled properly when used after the ``debug`` command with `pdb++`_. + + .. _pdb++: https://pypi.org/project/pdbpp/ + + +- `#4975 <https://github.com/pytest-dev/pytest/issues/4975>`_: Fix the interpretation of ``-qq`` option where it was being considered as ``-v`` instead. + + +- `#4978 <https://github.com/pytest-dev/pytest/issues/4978>`_: ``outcomes.Exit`` is not swallowed in ``assertrepr_compare`` anymore. + + +- `#4988 <https://github.com/pytest-dev/pytest/issues/4988>`_: Close logging's file handler explicitly when the session finishes. + + +- `#5003 <https://github.com/pytest-dev/pytest/issues/5003>`_: Fix line offset with mark collection error (off by one). + + + +Improved Documentation +---------------------- + +- `#4974 <https://github.com/pytest-dev/pytest/issues/4974>`_: Update docs for ``pytest_cmdline_parse`` hook to note availability liminations + + + +Trivial/Internal Changes +------------------------ + +- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: ``pluggy>=0.9`` is now required. + + +- `#4815 <https://github.com/pytest-dev/pytest/issues/4815>`_: ``funcsigs>=1.0`` is now required for Python 2.7. + + +- `#4829 <https://github.com/pytest-dev/pytest/issues/4829>`_: Some left-over internal code related to ``yield`` tests has been removed. + + +- `#4890 <https://github.com/pytest-dev/pytest/issues/4890>`_: Remove internally unused ``anypython`` fixture from the pytester plugin. + + +- `#4912 <https://github.com/pytest-dev/pytest/issues/4912>`_: Remove deprecated Sphinx directive, ``add_description_unit()``, + pin sphinx-removed-in to >= 0.2.0 to support Sphinx 2.0. + + +- `#4913 <https://github.com/pytest-dev/pytest/issues/4913>`_: Fix pytest tests invocation with custom ``PYTHONPATH``. + + +- `#4965 <https://github.com/pytest-dev/pytest/issues/4965>`_: New ``pytest_report_to_serializable`` and ``pytest_report_from_serializable`` **experimental** hooks. + + These hooks will be used by ``pytest-xdist``, ``pytest-subtests``, and the replacement for + resultlog to serialize and customize reports. + + They are experimental, meaning that their details might change or even be removed + completely in future patch releases without warning. + + Feedback is welcome from plugin authors and users alike. + + +- `#4987 <https://github.com/pytest-dev/pytest/issues/4987>`_: ``Collector.repr_failure`` respects the ``--tb`` option, but only defaults to ``short`` now (with ``auto``). + + +pytest 4.3.1 (2019-03-11) +========================= + +Bug Fixes +--------- + +- `#4810 <https://github.com/pytest-dev/pytest/issues/4810>`_: Logging messages inside ``pytest_runtest_logreport()`` are now properly captured and displayed. + + +- `#4861 <https://github.com/pytest-dev/pytest/issues/4861>`_: Improve validation of contents written to captured output so it behaves the same as when capture is disabled. + + +- `#4898 <https://github.com/pytest-dev/pytest/issues/4898>`_: Fix ``AttributeError: FixtureRequest has no 'confg' attribute`` bug in ``testdir.copy_example``. + + + +Trivial/Internal Changes +------------------------ + +- `#4768 <https://github.com/pytest-dev/pytest/issues/4768>`_: Avoid pkg_resources import at the top-level. + + +pytest 4.3.0 (2019-02-16) +========================= + +Deprecations +------------ + +- `#4724 <https://github.com/pytest-dev/pytest/issues/4724>`_: ``pytest.warns()`` now emits a warning when it receives unknown keyword arguments. + + This will be changed into an error in the future. + + + +Features +-------- + +- `#2753 <https://github.com/pytest-dev/pytest/issues/2753>`_: Usage errors from argparse are mapped to pytest's ``UsageError``. + + +- `#3711 <https://github.com/pytest-dev/pytest/issues/3711>`_: Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards. + Add the :globalvar:`collect_ignore_glob` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards. + + +- `#4698 <https://github.com/pytest-dev/pytest/issues/4698>`_: The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed. + + In the end it was considered to be more + of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not + install pytest 5.0 on those interpreters. + + +- `#4707 <https://github.com/pytest-dev/pytest/issues/4707>`_: With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks. + + + +Bug Fixes +--------- + +- `#4651 <https://github.com/pytest-dev/pytest/issues/4651>`_: ``--help`` and ``--version`` are handled with ``UsageError``. + + +- `#4782 <https://github.com/pytest-dev/pytest/issues/4782>`_: Fix ``AssertionError`` with collection of broken symlinks with packages. + + +pytest 4.2.1 (2019-02-12) +========================= + +Bug Fixes +--------- + +- `#2895 <https://github.com/pytest-dev/pytest/issues/2895>`_: The ``pytest_report_collectionfinish`` hook now is also called with ``--collect-only``. + + +- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Do not raise ``UsageError`` when an imported package has a ``pytest_plugins.py`` child module. + + +- `#4347 <https://github.com/pytest-dev/pytest/issues/4347>`_: Fix output capturing when using pdb++ with recursive debugging. + + +- `#4592 <https://github.com/pytest-dev/pytest/issues/4592>`_: Fix handling of ``collect_ignore`` via parent ``conftest.py``. + + +- `#4700 <https://github.com/pytest-dev/pytest/issues/4700>`_: Fix regression where ``setUpClass`` would always be called in subclasses even if all tests + were skipped by a ``unittest.skip()`` decorator applied in the subclass. + + +- `#4739 <https://github.com/pytest-dev/pytest/issues/4739>`_: Fix ``parametrize(... ids=<function>)`` when the function returns non-strings. + + +- `#4745 <https://github.com/pytest-dev/pytest/issues/4745>`_: Fix/improve collection of args when passing in ``__init__.py`` and a test file. + + +- `#4770 <https://github.com/pytest-dev/pytest/issues/4770>`_: ``more_itertools`` is now constrained to <6.0.0 when required for Python 2.7 compatibility. + + +- `#526 <https://github.com/pytest-dev/pytest/issues/526>`_: Fix "ValueError: Plugin already registered" exceptions when running in build directories that symlink to actual source. + + + +Improved Documentation +---------------------- + +- `#3899 <https://github.com/pytest-dev/pytest/issues/3899>`_: Add note to ``plugins.rst`` that ``pytest_plugins`` should not be used as a name for a user module containing plugins. + + +- `#4324 <https://github.com/pytest-dev/pytest/issues/4324>`_: Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises. + + +- `#4709 <https://github.com/pytest-dev/pytest/issues/4709>`_: Document how to customize test failure messages when using + ``pytest.warns``. + + + +Trivial/Internal Changes +------------------------ + +- `#4741 <https://github.com/pytest-dev/pytest/issues/4741>`_: Some verbosity related attributes of the TerminalReporter plugin are now + read only properties. + + +pytest 4.2.0 (2019-01-30) +========================= + +Features +-------- + +- `#3094 <https://github.com/pytest-dev/pytest/issues/3094>`_: `Classic xunit-style <https://docs.pytest.org/en/stable/xunit_setup.html>`__ functions and methods + now obey the scope of *autouse* fixtures. + + This fixes a number of surprising issues like ``setup_method`` being called before session-scoped + autouse fixtures (see `#517 <https://github.com/pytest-dev/pytest/issues/517>`__ for an example). + + +- `#4627 <https://github.com/pytest-dev/pytest/issues/4627>`_: Display a message at the end of the test session when running under Python 2.7 and 3.4 that pytest 5.0 will no longer + support those Python versions. + + +- `#4660 <https://github.com/pytest-dev/pytest/issues/4660>`_: The number of *selected* tests now are also displayed when the ``-k`` or ``-m`` flags are used. + + +- `#4688 <https://github.com/pytest-dev/pytest/issues/4688>`_: ``pytest_report_teststatus`` hook now can also receive a ``config`` parameter. + + +- `#4691 <https://github.com/pytest-dev/pytest/issues/4691>`_: ``pytest_terminal_summary`` hook now can also receive a ``config`` parameter. + + + +Bug Fixes +--------- + +- `#3547 <https://github.com/pytest-dev/pytest/issues/3547>`_: ``--junitxml`` can emit XML compatible with Jenkins xUnit. + ``junit_family`` INI option accepts ``legacy|xunit1``, which produces old style output, and ``xunit2`` that conforms more strictly to https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd + + +- `#4280 <https://github.com/pytest-dev/pytest/issues/4280>`_: Improve quitting from pdb, especially with ``--trace``. + + Using ``q[quit]`` after ``pdb.set_trace()`` will quit pytest also. + + +- `#4402 <https://github.com/pytest-dev/pytest/issues/4402>`_: Warning summary now groups warnings by message instead of by test id. + + This makes the output more compact and better conveys the general idea of how much code is + actually generating warnings, instead of how many tests call that code. + + +- `#4536 <https://github.com/pytest-dev/pytest/issues/4536>`_: ``monkeypatch.delattr`` handles class descriptors like ``staticmethod``/``classmethod``. + + +- `#4649 <https://github.com/pytest-dev/pytest/issues/4649>`_: Restore marks being considered keywords for keyword expressions. + + +- `#4653 <https://github.com/pytest-dev/pytest/issues/4653>`_: ``tmp_path`` fixture and other related ones provides resolved path (a.k.a real path) + + +- `#4667 <https://github.com/pytest-dev/pytest/issues/4667>`_: ``pytest_terminal_summary`` uses result from ``pytest_report_teststatus`` hook, rather than hardcoded strings. + + +- `#4669 <https://github.com/pytest-dev/pytest/issues/4669>`_: Correctly handle ``unittest.SkipTest`` exception containing non-ascii characters on Python 2. + + +- `#4680 <https://github.com/pytest-dev/pytest/issues/4680>`_: Ensure the ``tmpdir`` and the ``tmp_path`` fixtures are the same folder. + + +- `#4681 <https://github.com/pytest-dev/pytest/issues/4681>`_: Ensure ``tmp_path`` is always a real path. + + + +Trivial/Internal Changes +------------------------ + +- `#4643 <https://github.com/pytest-dev/pytest/issues/4643>`_: Use ``a.item()`` instead of the deprecated ``np.asscalar(a)`` in ``pytest.approx``. + + ``np.asscalar`` has been `deprecated <https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations>`__ in ``numpy 1.16.``. + + +- `#4657 <https://github.com/pytest-dev/pytest/issues/4657>`_: Copy saferepr from pylib + + +- `#4668 <https://github.com/pytest-dev/pytest/issues/4668>`_: The verbose word for expected failures in the teststatus report changes from ``xfail`` to ``XFAIL`` to be consistent with other test outcomes. + + +pytest 4.1.1 (2019-01-12) +========================= + +Bug Fixes +--------- + +- `#2256 <https://github.com/pytest-dev/pytest/issues/2256>`_: Show full repr with ``assert a==b`` and ``-vv``. + + +- `#3456 <https://github.com/pytest-dev/pytest/issues/3456>`_: Extend Doctest-modules to ignore mock objects. + + +- `#4617 <https://github.com/pytest-dev/pytest/issues/4617>`_: Fixed ``pytest.warns`` bug when context manager is reused (e.g. multiple parametrization). + + +- `#4631 <https://github.com/pytest-dev/pytest/issues/4631>`_: Don't rewrite assertion when ``__getattr__`` is broken + + + +Improved Documentation +---------------------- + +- `#3375 <https://github.com/pytest-dev/pytest/issues/3375>`_: Document that using ``setup.cfg`` may crash other tools or cause hard to track down problems because it uses a different parser than ``pytest.ini`` or ``tox.ini`` files. + + + +Trivial/Internal Changes +------------------------ + +- `#4602 <https://github.com/pytest-dev/pytest/issues/4602>`_: Uninstall ``hypothesis`` in regen tox env. + + +pytest 4.1.0 (2019-01-05) +========================= + +Removals +-------- + +- `#2169 <https://github.com/pytest-dev/pytest/issues/2169>`_: ``pytest.mark.parametrize``: in previous versions, errors raised by id functions were suppressed and changed into warnings. Now the exceptions are propagated, along with a pytest message informing the node, parameter value and index where the exception occurred. + + +- `#3078 <https://github.com/pytest-dev/pytest/issues/3078>`_: Remove legacy internal warnings system: ``config.warn``, ``Node.warn``. The ``pytest_logwarning`` now issues a warning when implemented. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#config-warn-and-node-warn>`__ on information on how to update your code. + + +- `#3079 <https://github.com/pytest-dev/pytest/issues/3079>`_: Removed support for yield tests - they are fundamentally broken because they don't support fixtures properly since collection and test execution were separated. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#yield-tests>`__ on information on how to update your code. + + +- `#3082 <https://github.com/pytest-dev/pytest/issues/3082>`_: Removed support for applying marks directly to values in ``@pytest.mark.parametrize``. Use ``pytest.param`` instead. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#marks-in-pytest-mark-parametrize>`__ on information on how to update your code. + + +- `#3083 <https://github.com/pytest-dev/pytest/issues/3083>`_: Removed ``Metafunc.addcall``. This was the predecessor mechanism to ``@pytest.mark.parametrize``. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#metafunc-addcall>`__ on information on how to update your code. + + +- `#3085 <https://github.com/pytest-dev/pytest/issues/3085>`_: Removed support for passing strings to ``pytest.main``. Now, always pass a list of strings instead. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#passing-command-line-string-to-pytest-main>`__ on information on how to update your code. + + +- `#3086 <https://github.com/pytest-dev/pytest/issues/3086>`_: ``[pytest]`` section in **setup.cfg** files is no longer supported, use ``[tool:pytest]`` instead. ``setup.cfg`` files + are meant for use with ``distutils``, and a section named ``pytest`` has notoriously been a source of conflicts and bugs. + + Note that for **pytest.ini** and **tox.ini** files the section remains ``[pytest]``. + + +- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: Removed the deprecated compat properties for ``node.Class/Function/Module`` - use ``pytest.Class/Function/Module`` now. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#internal-classes-accessed-through-node>`__ on information on how to update your code. + + +- `#4421 <https://github.com/pytest-dev/pytest/issues/4421>`_: Removed the implementation of the ``pytest_namespace`` hook. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#pytest-namespace>`__ on information on how to update your code. + + +- `#4489 <https://github.com/pytest-dev/pytest/issues/4489>`_: Removed ``request.cached_setup``. This was the predecessor mechanism to modern fixtures. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#cached-setup>`__ on information on how to update your code. + + +- `#4535 <https://github.com/pytest-dev/pytest/issues/4535>`_: Removed the deprecated ``PyCollector.makeitem`` method. This method was made public by mistake a long time ago. + + +- `#4543 <https://github.com/pytest-dev/pytest/issues/4543>`_: Removed support to define fixtures using the ``pytest_funcarg__`` prefix. Use the ``@pytest.fixture`` decorator instead. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#pytest-funcarg-prefix>`__ on information on how to update your code. + + +- `#4545 <https://github.com/pytest-dev/pytest/issues/4545>`_: Calling fixtures directly is now always an error instead of a warning. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly>`__ on information on how to update your code. + + +- `#4546 <https://github.com/pytest-dev/pytest/issues/4546>`_: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check. + + Use ``Node.get_closest_marker(name)`` as a replacement. + + +- `#4547 <https://github.com/pytest-dev/pytest/issues/4547>`_: The deprecated ``record_xml_property`` fixture has been removed, use the more generic ``record_property`` instead. + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#record-xml-property>`__ for more information. + + +- `#4548 <https://github.com/pytest-dev/pytest/issues/4548>`_: An error is now raised if the ``pytest_plugins`` variable is defined in a non-top-level ``conftest.py`` file (i.e., not residing in the ``rootdir``). + + See our `docs <https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`__ for more information. + + +- `#891 <https://github.com/pytest-dev/pytest/issues/891>`_: Remove ``testfunction.markername`` attributes - use ``Node.iter_markers(name=None)`` to iterate them. + + + +Deprecations +------------ + +- `#3050 <https://github.com/pytest-dev/pytest/issues/3050>`_: Deprecated the ``pytest.config`` global. + + See https://docs.pytest.org/en/stable/deprecations.html#pytest-config-global for rationale. + + +- `#3974 <https://github.com/pytest-dev/pytest/issues/3974>`_: Passing the ``message`` parameter of ``pytest.raises`` now issues a ``DeprecationWarning``. + + It is a common mistake to think this parameter will match the exception message, while in fact + it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this + mistake and because it is believed to be little used, pytest is deprecating it without providing + an alternative for the moment. + + If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__. + + +- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Deprecated ``raises(..., 'code(as_a_string)')`` and ``warns(..., 'code(as_a_string)')``. + + See https://docs.pytest.org/en/stable/deprecations.html#raises-warns-exec for rationale and examples. + + + +Features +-------- + +- `#3191 <https://github.com/pytest-dev/pytest/issues/3191>`_: A warning is now issued when assertions are made for ``None``. + + This is a common source of confusion among new users, which write: + + .. code-block:: python + + assert mocked_object.assert_called_with(3, 4, 5, key="value") + + When they should write: + + .. code-block:: python + + mocked_object.assert_called_with(3, 4, 5, key="value") + + Because the ``assert_called_with`` method of mock objects already executes an assertion. + + This warning will not be issued when ``None`` is explicitly checked. An assertion like: + + .. code-block:: python + + assert variable is None + + will not issue the warning. + + +- `#3632 <https://github.com/pytest-dev/pytest/issues/3632>`_: Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs <http://www.attrs.org/en/stable/>`__ or `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ (Python 3.7+, `backported to 3.6 <https://pypi.org/project/dataclasses>`__). + + +- `#4278 <https://github.com/pytest-dev/pytest/issues/4278>`_: ``CACHEDIR.TAG`` files are now created inside cache directories. + + Those files are part of the `Cache Directory Tagging Standard <http://www.bford.info/cachedir/spec.html>`__, and can + be used by backup or synchronization programs to identify pytest's cache directory as such. + + +- `#4292 <https://github.com/pytest-dev/pytest/issues/4292>`_: ``pytest.outcomes.Exit`` is derived from ``SystemExit`` instead of ``KeyboardInterrupt``. This allows us to better handle ``pdb`` exiting. + + +- `#4371 <https://github.com/pytest-dev/pytest/issues/4371>`_: Updated the ``--collect-only`` option to display test descriptions when ran using ``--verbose``. + + +- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Restructured ``ExceptionInfo`` object construction and ensure incomplete instances have a ``repr``/``str``. + + +- `#4416 <https://github.com/pytest-dev/pytest/issues/4416>`_: pdb: added support for keyword arguments with ``pdb.set_trace``. + + It handles ``header`` similar to Python 3.7 does it, and forwards any + other keyword arguments to the ``Pdb`` constructor. + + This allows for ``__import__("pdb").set_trace(skip=["foo.*"])``. + + +- `#4483 <https://github.com/pytest-dev/pytest/issues/4483>`_: Added ini parameter ``junit_duration_report`` to optionally report test call durations, excluding setup and teardown times. + + The JUnit XML specification and the default pytest behavior is to include setup and teardown times in the test duration + report. You can include just the call durations instead (excluding setup and teardown) by adding this to your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + junit_duration_report = call + + +- `#4532 <https://github.com/pytest-dev/pytest/issues/4532>`_: ``-ra`` now will show errors and failures last, instead of as the first items in the summary. + + This makes it easier to obtain a list of errors and failures to run tests selectively. + + +- `#4599 <https://github.com/pytest-dev/pytest/issues/4599>`_: ``pytest.importorskip`` now supports a ``reason`` parameter, which will be shown when the + requested module cannot be imported. + + + +Bug Fixes +--------- + +- `#3532 <https://github.com/pytest-dev/pytest/issues/3532>`_: ``-p`` now accepts its argument without a space between the value, for example ``-pmyplugin``. + + +- `#4327 <https://github.com/pytest-dev/pytest/issues/4327>`_: ``approx`` again works with more generic containers, more precisely instances of ``Iterable`` and ``Sized`` instead of more restrictive ``Sequence``. + + +- `#4397 <https://github.com/pytest-dev/pytest/issues/4397>`_: Ensure that node ids are printable. + + +- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fixed ``raises(..., 'code(string)')`` frame filename. + + +- `#4458 <https://github.com/pytest-dev/pytest/issues/4458>`_: Display actual test ids in ``--collect-only``. + + + +Improved Documentation +---------------------- + +- `#4557 <https://github.com/pytest-dev/pytest/issues/4557>`_: Markers example documentation page updated to support latest pytest version. + + +- `#4558 <https://github.com/pytest-dev/pytest/issues/4558>`_: Update cache documentation example to correctly show cache hit and miss. + + +- `#4580 <https://github.com/pytest-dev/pytest/issues/4580>`_: Improved detailed summary report documentation. + + + +Trivial/Internal Changes +------------------------ + +- `#4447 <https://github.com/pytest-dev/pytest/issues/4447>`_: Changed the deprecation type of ``--result-log`` to ``PytestDeprecationWarning``. + + It was decided to remove this feature at the next major revision. + + +pytest 4.0.2 (2018-12-13) +========================= + +Bug Fixes +--------- + +- `#4265 <https://github.com/pytest-dev/pytest/issues/4265>`_: Validate arguments from the ``PYTEST_ADDOPTS`` environment variable and the ``addopts`` ini option separately. + + +- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fix ``raises(..., 'code(string)')`` frame filename. + + +- `#4500 <https://github.com/pytest-dev/pytest/issues/4500>`_: When a fixture yields and a log call is made after the test runs, and, if the test is interrupted, capture attributes are ``None``. + + +- `#4538 <https://github.com/pytest-dev/pytest/issues/4538>`_: Raise ``TypeError`` for ``with raises(..., match=<non-None falsey value>)``. + + + +Improved Documentation +---------------------- + +- `#1495 <https://github.com/pytest-dev/pytest/issues/1495>`_: Document common doctest fixture directory tree structure pitfalls + + +pytest 4.0.1 (2018-11-23) +========================= + +Bug Fixes +--------- + +- `#3952 <https://github.com/pytest-dev/pytest/issues/3952>`_: Display warnings before "short test summary info" again, but still later warnings in the end. + + +- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Handle uninitialized exceptioninfo in repr/str. + + +- `#4393 <https://github.com/pytest-dev/pytest/issues/4393>`_: Do not create ``.gitignore``/``README.md`` files in existing cache directories. + + +- `#4400 <https://github.com/pytest-dev/pytest/issues/4400>`_: Rearrange warning handling for the yield test errors so the opt-out in 4.0.x correctly works. + + +- `#4405 <https://github.com/pytest-dev/pytest/issues/4405>`_: Fix collection of testpaths with ``--pyargs``. + + +- `#4412 <https://github.com/pytest-dev/pytest/issues/4412>`_: Fix assertion rewriting involving ``Starred`` + side-effects. + + +- `#4425 <https://github.com/pytest-dev/pytest/issues/4425>`_: Ensure we resolve the absolute path when the given ``--basetemp`` is a relative path. + + + +Trivial/Internal Changes +------------------------ + +- `#4315 <https://github.com/pytest-dev/pytest/issues/4315>`_: Use ``pkg_resources.parse_version`` instead of ``LooseVersion`` in minversion check. + + +- `#4440 <https://github.com/pytest-dev/pytest/issues/4440>`_: Adjust the stack level of some internal pytest warnings. + + +pytest 4.0.0 (2018-11-13) +========================= + +Removals +-------- + +- `#3737 <https://github.com/pytest-dev/pytest/issues/3737>`_: **RemovedInPytest4Warnings are now errors by default.** + + Following our plan to remove deprecated features with as little disruption as + possible, all warnings of type ``RemovedInPytest4Warnings`` now generate errors + instead of warning messages. + + **The affected features will be effectively removed in pytest 4.1**, so please consult the + `Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`__ + section in the docs for directions on how to update existing code. + + In the pytest ``4.0.X`` series, it is possible to change the errors back into warnings as a stop + gap measure by adding this to your ``pytest.ini`` file: + + .. code-block:: ini + + [pytest] + filterwarnings = + ignore::pytest.RemovedInPytest4Warning + + But this will stop working when pytest ``4.1`` is released. + + **If you have concerns** about the removal of a specific feature, please add a + comment to `#4348 <https://github.com/pytest-dev/pytest/issues/4348>`__. + + +- `#4358 <https://github.com/pytest-dev/pytest/issues/4358>`_: Remove the ``::()`` notation to denote a test class instance in node ids. + + Previously, node ids that contain test instances would use ``::()`` to denote the instance like this:: + + test_foo.py::Test::()::test_bar + + The extra ``::()`` was puzzling to most users and has been removed, so that the test id becomes now:: + + test_foo.py::Test::test_bar + + This change could not accompany a deprecation period as is usual when user-facing functionality changes because + it was not really possible to detect when the functionality was being used explicitly. + + The extra ``::()`` might have been removed in some places internally already, + which then led to confusion in places where it was expected, e.g. with + ``--deselect`` (`#4127 <https://github.com/pytest-dev/pytest/issues/4127>`_). + + Test class instances are also not listed with ``--collect-only`` anymore. + + + +Features +-------- + +- `#4270 <https://github.com/pytest-dev/pytest/issues/4270>`_: The ``cache_dir`` option uses ``$TOX_ENV_DIR`` as prefix (if set in the environment). + + This uses a different cache per tox environment by default. + + + +Bug Fixes +--------- + +- `#3554 <https://github.com/pytest-dev/pytest/issues/3554>`_: Fix ``CallInfo.__repr__`` for when the call is not finished yet. + + +pytest 3.10.1 (2018-11-11) +========================== + +Bug Fixes +--------- + +- `#4287 <https://github.com/pytest-dev/pytest/issues/4287>`_: Fix nested usage of debugging plugin (pdb), e.g. with pytester's ``testdir.runpytest``. + + +- `#4304 <https://github.com/pytest-dev/pytest/issues/4304>`_: Block the ``stepwise`` plugin if ``cacheprovider`` is also blocked, as one depends on the other. + + +- `#4306 <https://github.com/pytest-dev/pytest/issues/4306>`_: Parse ``minversion`` as an actual version and not as dot-separated strings. + + +- `#4310 <https://github.com/pytest-dev/pytest/issues/4310>`_: Fix duplicate collection due to multiple args matching the same packages. + + +- `#4321 <https://github.com/pytest-dev/pytest/issues/4321>`_: Fix ``item.nodeid`` with resolved symlinks. + + +- `#4325 <https://github.com/pytest-dev/pytest/issues/4325>`_: Fix collection of direct symlinked files, where the target does not match ``python_files``. + + +- `#4329 <https://github.com/pytest-dev/pytest/issues/4329>`_: Fix TypeError in report_collect with _collect_report_last_write. + + + +Trivial/Internal Changes +------------------------ + +- `#4305 <https://github.com/pytest-dev/pytest/issues/4305>`_: Replace byte/unicode helpers in test_capture with python level syntax. + + +pytest 3.10.0 (2018-11-03) +========================== + +Features +-------- + +- `#2619 <https://github.com/pytest-dev/pytest/issues/2619>`_: Resume capturing output after ``continue`` with ``__import__("pdb").set_trace()``. + + This also adds a new ``pytest_leave_pdb`` hook, and passes in ``pdb`` to the + existing ``pytest_enter_pdb`` hook. + + +- `#4147 <https://github.com/pytest-dev/pytest/issues/4147>`_: Add ``--sw``, ``--stepwise`` as an alternative to ``--lf -x`` for stopping at the first failure, but starting the next test invocation from that test. See `the documentation <https://docs.pytest.org/en/stable/cache.html#stepwise>`__ for more info. + + +- `#4188 <https://github.com/pytest-dev/pytest/issues/4188>`_: Make ``--color`` emit colorful dots when not running in verbose mode. Earlier, it would only colorize the test-by-test output if ``--verbose`` was also passed. + + +- `#4225 <https://github.com/pytest-dev/pytest/issues/4225>`_: Improve performance with collection reporting in non-quiet mode with terminals. + + The "collecting …" message is only printed/updated every 0.5s. + + + +Bug Fixes +--------- + +- `#2701 <https://github.com/pytest-dev/pytest/issues/2701>`_: Fix false ``RemovedInPytest4Warning: usage of Session... is deprecated, please use pytest`` warnings. + + +- `#4046 <https://github.com/pytest-dev/pytest/issues/4046>`_: Fix problems with running tests in package ``__init__.py`` files. + + +- `#4260 <https://github.com/pytest-dev/pytest/issues/4260>`_: Swallow warnings during anonymous compilation of source. + + +- `#4262 <https://github.com/pytest-dev/pytest/issues/4262>`_: Fix access denied error when deleting stale directories created by ``tmpdir`` / ``tmp_path``. + + +- `#611 <https://github.com/pytest-dev/pytest/issues/611>`_: Naming a fixture ``request`` will now raise a warning: the ``request`` fixture is internal and + should not be overwritten as it will lead to internal errors. + +- `#4266 <https://github.com/pytest-dev/pytest/issues/4266>`_: Handle (ignore) exceptions raised during collection, e.g. with Django's LazySettings proxy class. + + + +Improved Documentation +---------------------- + +- `#4255 <https://github.com/pytest-dev/pytest/issues/4255>`_: Added missing documentation about the fact that module names passed to filter warnings are not regex-escaped. + + + +Trivial/Internal Changes +------------------------ + +- `#4272 <https://github.com/pytest-dev/pytest/issues/4272>`_: Display cachedir also in non-verbose mode if non-default. + + +- `#4277 <https://github.com/pytest-dev/pytest/issues/4277>`_: pdb: improve message about output capturing with ``set_trace``. + + Do not display "IO-capturing turned off/on" when ``-s`` is used to avoid + confusion. + + +- `#4279 <https://github.com/pytest-dev/pytest/issues/4279>`_: Improve message and stack level of warnings issued by ``monkeypatch.setenv`` when the value of the environment variable is not a ``str``. + + +pytest 3.9.3 (2018-10-27) +========================= + +Bug Fixes +--------- + +- `#4174 <https://github.com/pytest-dev/pytest/issues/4174>`_: Fix "ValueError: Plugin already registered" with conftest plugins via symlink. + + +- `#4181 <https://github.com/pytest-dev/pytest/issues/4181>`_: Handle race condition between creation and deletion of temporary folders. + + +- `#4221 <https://github.com/pytest-dev/pytest/issues/4221>`_: Fix bug where the warning summary at the end of the test session was not showing the test where the warning was originated. + + +- `#4243 <https://github.com/pytest-dev/pytest/issues/4243>`_: Fix regression when ``stacklevel`` for warnings was passed as positional argument on python2. + + + +Improved Documentation +---------------------- + +- `#3851 <https://github.com/pytest-dev/pytest/issues/3851>`_: Add reference to ``empty_parameter_set_mark`` ini option in documentation of ``@pytest.mark.parametrize`` + + + +Trivial/Internal Changes +------------------------ + +- `#4028 <https://github.com/pytest-dev/pytest/issues/4028>`_: Revert patching of ``sys.breakpointhook`` since it appears to do nothing. + + +- `#4233 <https://github.com/pytest-dev/pytest/issues/4233>`_: Apply an import sorter (``reorder-python-imports``) to the codebase. + + +- `#4248 <https://github.com/pytest-dev/pytest/issues/4248>`_: Remove use of unnecessary compat shim, six.binary_type + + +pytest 3.9.2 (2018-10-22) +========================= + +Bug Fixes +--------- + +- `#2909 <https://github.com/pytest-dev/pytest/issues/2909>`_: Improve error message when a recursive dependency between fixtures is detected. + + +- `#3340 <https://github.com/pytest-dev/pytest/issues/3340>`_: Fix logging messages not shown in hooks ``pytest_sessionstart()`` and ``pytest_sessionfinish()``. + + +- `#3533 <https://github.com/pytest-dev/pytest/issues/3533>`_: Fix unescaped XML raw objects in JUnit report for skipped tests + + +- `#3691 <https://github.com/pytest-dev/pytest/issues/3691>`_: Python 2: safely format warning message about passing unicode strings to ``warnings.warn``, which may cause + surprising ``MemoryError`` exception when monkey patching ``warnings.warn`` itself. + + +- `#4026 <https://github.com/pytest-dev/pytest/issues/4026>`_: Improve error message when it is not possible to determine a function's signature. + + +- `#4177 <https://github.com/pytest-dev/pytest/issues/4177>`_: Pin ``setuptools>=40.0`` to support ``py_modules`` in ``setup.cfg`` + + +- `#4179 <https://github.com/pytest-dev/pytest/issues/4179>`_: Restore the tmpdir behaviour of symlinking the current test run. + + +- `#4192 <https://github.com/pytest-dev/pytest/issues/4192>`_: Fix filename reported by ``warnings.warn`` when using ``recwarn`` under python2. + + +pytest 3.9.1 (2018-10-16) +========================= + +Features +-------- + +- `#4159 <https://github.com/pytest-dev/pytest/issues/4159>`_: For test-suites containing test classes, the information about the subclassed + module is now output only if a higher verbosity level is specified (at least + "-vv"). + + +pytest 3.9.0 (2018-10-15 - not published due to a release automation bug) +========================================================================= + +Deprecations +------------ + +- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings. + + * Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now + users will this warning:: + + usage of Function.Module is deprecated, please use pytest.Module instead + + Users should just ``import pytest`` and access those objects using the ``pytest`` module. + + * ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can + consult `funcarg comparison section in the docs <https://docs.pytest.org/en/stable/funcarg_compare.html>`_. + + * Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` + subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during + collection. + + This issue should affect only advanced plugins who create new collection types, so if you see this warning + message please contact the authors so they can change the code. + + * The warning that produces the message below has changed to ``RemovedInPytest4Warning``:: + + getfuncargvalue is deprecated, use getfixturevalue + + +- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Add a Deprecation warning for pytest.ensuretemp as it was deprecated since a while. + + + +Features +-------- + +- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: Improve usage errors messages by hiding internal details which can be distracting and noisy. + + This has the side effect that some error conditions that previously raised generic errors (such as + ``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions. + + +- `#3332 <https://github.com/pytest-dev/pytest/issues/3332>`_: Improve the error displayed when a ``conftest.py`` file could not be imported. + + In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr`` + to show or hide chained tracebacks in Python 3 (defaults to ``True``). + + +- `#3849 <https://github.com/pytest-dev/pytest/issues/3849>`_: Add ``empty_parameter_set_mark=fail_at_collect`` ini option for raising an exception when parametrize collects an empty set. + + +- `#3964 <https://github.com/pytest-dev/pytest/issues/3964>`_: Log messages generated in the collection phase are shown when + live-logging is enabled and/or when they are logged to a file. + + +- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object. Also introduce ``tmp_path_factory`` as + a session-scoped fixture for creating arbitrary temporary directories from any other fixture or test. + + +- `#4013 <https://github.com/pytest-dev/pytest/issues/4013>`_: Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version + any customization would override pytest's filters and deprecation warnings would fall back to being hidden by default. + + +- `#4073 <https://github.com/pytest-dev/pytest/issues/4073>`_: Allow specification of timeout for ``Testdir.runpytest_subprocess()`` and ``Testdir.run()``. + + +- `#4098 <https://github.com/pytest-dev/pytest/issues/4098>`_: Add returncode argument to pytest.exit() to exit pytest with a specific return code. + + +- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument. + + This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead + of ``AssertionError``. + + +- `#4149 <https://github.com/pytest-dev/pytest/issues/4149>`_: Require setuptools>=30.3 and move most of the metadata to ``setup.cfg``. + + + +Bug Fixes +--------- + +- `#2535 <https://github.com/pytest-dev/pytest/issues/2535>`_: Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture. + + +- `#3057 <https://github.com/pytest-dev/pytest/issues/3057>`_: ``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``. + + +- `#3946 <https://github.com/pytest-dev/pytest/issues/3946>`_: Warning filters passed as command line options using ``-W`` now take precedence over filters defined in ``ini`` + configuration files. + + +- `#4066 <https://github.com/pytest-dev/pytest/issues/4066>`_: Fix source reindenting by using ``textwrap.dedent`` directly. + + +- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: ``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised. + + +- `#4108 <https://github.com/pytest-dev/pytest/issues/4108>`_: Resolve symbolic links for args. + + This fixes running ``pytest tests/test_foo.py::test_bar``, where ``tests`` + is a symlink to ``project/app/tests``: + previously ``project/app/conftest.py`` would be ignored for fixtures then. + + +- `#4132 <https://github.com/pytest-dev/pytest/issues/4132>`_: Fix duplicate printing of internal errors when using ``--pdb``. + + +- `#4135 <https://github.com/pytest-dev/pytest/issues/4135>`_: pathlib based tmpdir cleanup now correctly handles symlinks in the folder. + + +- `#4152 <https://github.com/pytest-dev/pytest/issues/4152>`_: Display the filename when encountering ``SyntaxWarning``. + + + +Improved Documentation +---------------------- + +- `#3713 <https://github.com/pytest-dev/pytest/issues/3713>`_: Update usefixtures documentation to clarify that it can't be used with fixture functions. + + +- `#4058 <https://github.com/pytest-dev/pytest/issues/4058>`_: Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for. + + +- `#4064 <https://github.com/pytest-dev/pytest/issues/4064>`_: According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest. + + +- `#4151 <https://github.com/pytest-dev/pytest/issues/4151>`_: Add tempir testing example to CONTRIBUTING.rst guide + + + +Trivial/Internal Changes +------------------------ + +- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: The internal ``MarkerError`` exception has been removed. + + +- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Port the implementation of tmpdir to pathlib. + + +- `#4063 <https://github.com/pytest-dev/pytest/issues/4063>`_: Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line. + + +- `#4093 <https://github.com/pytest-dev/pytest/issues/4093>`_: Fixed formatting of string literals in internal tests. + + +pytest 3.8.2 (2018-10-02) +========================= + +Deprecations and Removals +------------------------- + +- `#4036 <https://github.com/pytest-dev/pytest/issues/4036>`_: The ``item`` parameter of ``pytest_warning_captured`` hook is now documented as deprecated. We realized only after + the ``3.8`` release that this parameter is incompatible with ``pytest-xdist``. + + Our policy is to not deprecate features during bug-fix releases, but in this case we believe it makes sense as we are + only documenting it as deprecated, without issuing warnings which might potentially break test suites. This will get + the word out that hook implementers should not use this parameter at all. + + In a future release ``item`` will always be ``None`` and will emit a proper warning when a hook implementation + makes use of it. + + + +Bug Fixes +--------- + +- `#3539 <https://github.com/pytest-dev/pytest/issues/3539>`_: Fix reload on assertion rewritten modules. + + +- `#4034 <https://github.com/pytest-dev/pytest/issues/4034>`_: The ``.user_properties`` attribute of ``TestReport`` objects is a list + of (name, value) tuples, but could sometimes be instantiated as a tuple + of tuples. It is now always a list. + + +- `#4039 <https://github.com/pytest-dev/pytest/issues/4039>`_: No longer issue warnings about using ``pytest_plugins`` in non-top-level directories when using ``--pyargs``: the + current ``--pyargs`` mechanism is not reliable and might give false negatives. + + +- `#4040 <https://github.com/pytest-dev/pytest/issues/4040>`_: Exclude empty reports for passed tests when ``-rP`` option is used. + + +- `#4051 <https://github.com/pytest-dev/pytest/issues/4051>`_: Improve error message when an invalid Python expression is passed to the ``-m`` option. + + +- `#4056 <https://github.com/pytest-dev/pytest/issues/4056>`_: ``MonkeyPatch.setenv`` and ``MonkeyPatch.delenv`` issue a warning if the environment variable name is not ``str`` on Python 2. + + In Python 2, adding ``unicode`` keys to ``os.environ`` causes problems with ``subprocess`` (and possible other modules), + making this a subtle bug specially susceptible when used with ``from __future__ import unicode_literals``. + + + +Improved Documentation +---------------------- + +- `#3928 <https://github.com/pytest-dev/pytest/issues/3928>`_: Add possible values for fixture scope to docs. + + +pytest 3.8.1 (2018-09-22) +========================= + +Bug Fixes +--------- + +- `#3286 <https://github.com/pytest-dev/pytest/issues/3286>`_: ``.pytest_cache`` directory is now automatically ignored by Git. Users who would like to contribute a solution for other SCMs please consult/comment on this issue. + + +- `#3749 <https://github.com/pytest-dev/pytest/issues/3749>`_: Fix the following error during collection of tests inside packages:: + + TypeError: object of type 'Package' has no len() + + +- `#3941 <https://github.com/pytest-dev/pytest/issues/3941>`_: Fix bug where indirect parametrization would consider the scope of all fixtures used by the test function to determine the parametrization scope, and not only the scope of the fixtures being parametrized. + + +- `#3973 <https://github.com/pytest-dev/pytest/issues/3973>`_: Fix crash of the assertion rewriter if a test changed the current working directory without restoring it afterwards. + + +- `#3998 <https://github.com/pytest-dev/pytest/issues/3998>`_: Fix issue that prevented some caplog properties (for example ``record_tuples``) from being available when entering the debugger with ``--pdb``. + + +- `#3999 <https://github.com/pytest-dev/pytest/issues/3999>`_: Fix ``UnicodeDecodeError`` in python2.x when a class returns a non-ascii binary ``__repr__`` in an assertion which also contains non-ascii text. + + + +Improved Documentation +---------------------- + +- `#3996 <https://github.com/pytest-dev/pytest/issues/3996>`_: New `Deprecations and Removals <https://docs.pytest.org/en/stable/deprecations.html>`_ page shows all currently + deprecated features, the rationale to do so, and alternatives to update your code. It also list features removed + from pytest in past major releases to help those with ancient pytest versions to upgrade. + + + +Trivial/Internal Changes +------------------------ + +- `#3955 <https://github.com/pytest-dev/pytest/issues/3955>`_: Improve pre-commit detection for changelog filenames + + +- `#3975 <https://github.com/pytest-dev/pytest/issues/3975>`_: Remove legacy code around im_func as that was python2 only + + +pytest 3.8.0 (2018-09-05) +========================= + +Deprecations and Removals +------------------------- + +- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: ``Config.warn`` and ``Node.warn`` have been + deprecated, see `<https://docs.pytest.org/en/stable/deprecations.html#config-warn-and-node-warn>`_ for rationale and + examples. + +- `#3936 <https://github.com/pytest-dev/pytest/issues/3936>`_: ``@pytest.mark.filterwarnings`` second parameter is no longer regex-escaped, + making it possible to actually use regular expressions to check the warning message. + + **Note**: regex-escaping the match string was an implementation oversight that might break test suites which depend + on the old behavior. + + + +Features +-------- + +- `#2452 <https://github.com/pytest-dev/pytest/issues/2452>`_: Internal pytest warnings are now issued using the standard ``warnings`` module, making it possible to use + the standard warnings filters to manage those warnings. This introduces ``PytestWarning``, + ``PytestDeprecationWarning`` and ``RemovedInPytest4Warning`` warning types as part of the public API. + + Consult `the documentation <https://docs.pytest.org/en/stable/warnings.html#internal-pytest-warnings>`__ for more info. + + +- `#2908 <https://github.com/pytest-dev/pytest/issues/2908>`_: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are now shown by default if no other warning filter is + configured. This makes pytest more compliant with + `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_. See + `the docs <https://docs.pytest.org/en/stable/warnings.html#deprecationwarning-and-pendingdeprecationwarning>`_ for + more info. + + +- `#3251 <https://github.com/pytest-dev/pytest/issues/3251>`_: Warnings are now captured and displayed during test collection. + + +- `#3784 <https://github.com/pytest-dev/pytest/issues/3784>`_: ``PYTEST_DISABLE_PLUGIN_AUTOLOAD`` environment variable disables plugin auto-loading when set. + + +- `#3829 <https://github.com/pytest-dev/pytest/issues/3829>`_: Added the ``count`` option to ``console_output_style`` to enable displaying the progress as a count instead of a percentage. + + +- `#3837 <https://github.com/pytest-dev/pytest/issues/3837>`_: Added support for 'xfailed' and 'xpassed' outcomes to the ``pytester.RunResult.assert_outcomes`` signature. + + + +Bug Fixes +--------- + +- `#3911 <https://github.com/pytest-dev/pytest/issues/3911>`_: Terminal writer now takes into account unicode character width when writing out progress. + + +- `#3913 <https://github.com/pytest-dev/pytest/issues/3913>`_: Pytest now returns with correct exit code (EXIT_USAGEERROR, 4) when called with unknown arguments. + + +- `#3918 <https://github.com/pytest-dev/pytest/issues/3918>`_: Improve performance of assertion rewriting. + + + +Improved Documentation +---------------------- + +- `#3566 <https://github.com/pytest-dev/pytest/issues/3566>`_: Added a blurb in usage.rst for the usage of -r flag which is used to show an extra test summary info. + + +- `#3907 <https://github.com/pytest-dev/pytest/issues/3907>`_: Corrected type of the exceptions collection passed to ``xfail``: ``raises`` argument accepts a ``tuple`` instead of ``list``. + + + +Trivial/Internal Changes +------------------------ + +- `#3853 <https://github.com/pytest-dev/pytest/issues/3853>`_: Removed ``"run all (no recorded failures)"`` message printed with ``--failed-first`` and ``--last-failed`` when there are no failed tests. + + +pytest 3.7.4 (2018-08-29) +========================= + +Bug Fixes +--------- + +- `#3506 <https://github.com/pytest-dev/pytest/issues/3506>`_: Fix possible infinite recursion when writing ``.pyc`` files. + + +- `#3853 <https://github.com/pytest-dev/pytest/issues/3853>`_: Cache plugin now obeys the ``-q`` flag when ``--last-failed`` and ``--failed-first`` flags are used. + + +- `#3883 <https://github.com/pytest-dev/pytest/issues/3883>`_: Fix bad console output when using ``console_output_style=classic``. + + +- `#3888 <https://github.com/pytest-dev/pytest/issues/3888>`_: Fix macOS specific code using ``capturemanager`` plugin in doctests. + + + +Improved Documentation +---------------------- + +- `#3902 <https://github.com/pytest-dev/pytest/issues/3902>`_: Fix pytest.org links + + +pytest 3.7.3 (2018-08-26) +========================= + +Bug Fixes +--------- + +- `#3033 <https://github.com/pytest-dev/pytest/issues/3033>`_: Fixtures during teardown can again use ``capsys`` and ``capfd`` to inspect output captured during tests. + + +- `#3773 <https://github.com/pytest-dev/pytest/issues/3773>`_: Fix collection of tests from ``__init__.py`` files if they match the ``python_files`` configuration option. + + +- `#3796 <https://github.com/pytest-dev/pytest/issues/3796>`_: Fix issue where teardown of fixtures of consecutive sub-packages were executed once, at the end of the outer + package. + + +- `#3816 <https://github.com/pytest-dev/pytest/issues/3816>`_: Fix bug where ``--show-capture=no`` option would still show logs printed during fixture teardown. + + +- `#3819 <https://github.com/pytest-dev/pytest/issues/3819>`_: Fix ``stdout/stderr`` not getting captured when real-time cli logging is active. + + +- `#3843 <https://github.com/pytest-dev/pytest/issues/3843>`_: Fix collection error when specifying test functions directly in the command line using ``test.py::test`` syntax together with ``--doctest-modules``. + + +- `#3848 <https://github.com/pytest-dev/pytest/issues/3848>`_: Fix bugs where unicode arguments could not be passed to ``testdir.runpytest`` on Python 2. + + +- `#3854 <https://github.com/pytest-dev/pytest/issues/3854>`_: Fix double collection of tests within packages when the filename starts with a capital letter. + + + +Improved Documentation +---------------------- + +- `#3824 <https://github.com/pytest-dev/pytest/issues/3824>`_: Added example for multiple glob pattern matches in ``python_files``. + + +- `#3833 <https://github.com/pytest-dev/pytest/issues/3833>`_: Added missing docs for ``pytester.Testdir``. + + +- `#3870 <https://github.com/pytest-dev/pytest/issues/3870>`_: Correct documentation for setuptools integration. + + + +Trivial/Internal Changes +------------------------ + +- `#3826 <https://github.com/pytest-dev/pytest/issues/3826>`_: Replace broken type annotations with type comments. + + +- `#3845 <https://github.com/pytest-dev/pytest/issues/3845>`_: Remove a reference to issue `#568 <https://github.com/pytest-dev/pytest/issues/568>`_ from the documentation, which has since been + fixed. + + +pytest 3.7.2 (2018-08-16) +========================= + +Bug Fixes +--------- + +- `#3671 <https://github.com/pytest-dev/pytest/issues/3671>`_: Fix ``filterwarnings`` not being registered as a builtin mark. + + +- `#3768 <https://github.com/pytest-dev/pytest/issues/3768>`_, `#3789 <https://github.com/pytest-dev/pytest/issues/3789>`_: Fix test collection from packages mixed with normal directories. + + +- `#3771 <https://github.com/pytest-dev/pytest/issues/3771>`_: Fix infinite recursion during collection if a ``pytest_ignore_collect`` hook returns ``False`` instead of ``None``. + + +- `#3774 <https://github.com/pytest-dev/pytest/issues/3774>`_: Fix bug where decorated fixtures would lose functionality (for example ``@mock.patch``). + + +- `#3775 <https://github.com/pytest-dev/pytest/issues/3775>`_: Fix bug where importing modules or other objects with prefix ``pytest_`` prefix would raise a ``PluginValidationError``. + + +- `#3788 <https://github.com/pytest-dev/pytest/issues/3788>`_: Fix ``AttributeError`` during teardown of ``TestCase`` subclasses which raise an exception during ``__init__``. + + +- `#3804 <https://github.com/pytest-dev/pytest/issues/3804>`_: Fix traceback reporting for exceptions with ``__cause__`` cycles. + + + +Improved Documentation +---------------------- + +- `#3746 <https://github.com/pytest-dev/pytest/issues/3746>`_: Add documentation for ``metafunc.config`` that had been mistakenly hidden. + + +pytest 3.7.1 (2018-08-02) +========================= + +Bug Fixes +--------- + +- `#3473 <https://github.com/pytest-dev/pytest/issues/3473>`_: Raise immediately if ``approx()`` is given an expected value of a type it doesn't understand (e.g. strings, nested dicts, etc.). + + +- `#3712 <https://github.com/pytest-dev/pytest/issues/3712>`_: Correctly represent the dimensions of a numpy array when calling ``repr()`` on ``approx()``. + +- `#3742 <https://github.com/pytest-dev/pytest/issues/3742>`_: Fix incompatibility with third party plugins during collection, which produced the error ``object has no attribute '_collectfile'``. + +- `#3745 <https://github.com/pytest-dev/pytest/issues/3745>`_: Display the absolute path if ``cache_dir`` is not relative to the ``rootdir`` instead of failing. + + +- `#3747 <https://github.com/pytest-dev/pytest/issues/3747>`_: Fix compatibility problem with plugins and the warning code issued by fixture functions when they are called directly. + + +- `#3748 <https://github.com/pytest-dev/pytest/issues/3748>`_: Fix infinite recursion in ``pytest.approx`` with arrays in ``numpy<1.13``. + + +- `#3757 <https://github.com/pytest-dev/pytest/issues/3757>`_: Pin pathlib2 to ``>=2.2.0`` as we require ``__fspath__`` support. + + +- `#3763 <https://github.com/pytest-dev/pytest/issues/3763>`_: Fix ``TypeError`` when the assertion message is ``bytes`` in python 3. + + +pytest 3.7.0 (2018-07-30) +========================= + +Deprecations and Removals +------------------------- + +- `#2639 <https://github.com/pytest-dev/pytest/issues/2639>`_: ``pytest_namespace`` has been `deprecated <https://docs.pytest.org/en/stable/deprecations.html#pytest-namespace>`_. + + +- `#3661 <https://github.com/pytest-dev/pytest/issues/3661>`_: Calling a fixture function directly, as opposed to request them in a test function, now issues a ``RemovedInPytest4Warning``. See `the documentation for rationale and examples <https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly>`_. + + + +Features +-------- + +- `#2283 <https://github.com/pytest-dev/pytest/issues/2283>`_: New ``package`` fixture scope: fixtures are finalized when the last test of a *package* finishes. This feature is considered **experimental**, so use it sparingly. + + +- `#3576 <https://github.com/pytest-dev/pytest/issues/3576>`_: ``Node.add_marker`` now supports an ``append=True/False`` parameter to determine whether the mark comes last (default) or first. + + +- `#3579 <https://github.com/pytest-dev/pytest/issues/3579>`_: Fixture ``caplog`` now has a ``messages`` property, providing convenient access to the format-interpolated log messages without the extra data provided by the formatter/handler. + + +- `#3610 <https://github.com/pytest-dev/pytest/issues/3610>`_: New ``--trace`` option to enter the debugger at the start of a test. + + +- `#3623 <https://github.com/pytest-dev/pytest/issues/3623>`_: Introduce ``pytester.copy_example`` as helper to do acceptance tests against examples from the project. + + + +Bug Fixes +--------- + +- `#2220 <https://github.com/pytest-dev/pytest/issues/2220>`_: Fix a bug where fixtures overridden by direct parameters (for example parametrization) were being instantiated even if they were not being used by a test. + + +- `#3695 <https://github.com/pytest-dev/pytest/issues/3695>`_: Fix ``ApproxNumpy`` initialisation argument mixup, ``abs`` and ``rel`` tolerances were flipped causing strange comparison results. + Add tests to check ``abs`` and ``rel`` tolerances for ``np.array`` and test for expecting ``nan`` with ``np.array()`` + + +- `#980 <https://github.com/pytest-dev/pytest/issues/980>`_: Fix truncated locals output in verbose mode. + + + +Improved Documentation +---------------------- + +- `#3295 <https://github.com/pytest-dev/pytest/issues/3295>`_: Correct the usage documentation of ``--last-failed-no-failures`` by adding the missing ``--last-failed`` argument in the presented examples, because they are misleading and lead to think that the missing argument is not needed. + + + +Trivial/Internal Changes +------------------------ + +- `#3519 <https://github.com/pytest-dev/pytest/issues/3519>`_: Now a ``README.md`` file is created in ``.pytest_cache`` to make it clear why the directory exists. + + +pytest 3.6.4 (2018-07-28) +========================= + +Bug Fixes +--------- + +- Invoke pytest using ``-mpytest`` so ``sys.path`` does not get polluted by packages installed in ``site-packages``. (`#742 <https://github.com/pytest-dev/pytest/issues/742>`_) + + +Improved Documentation +---------------------- + +- Use ``smtp_connection`` instead of ``smtp`` in fixtures documentation to avoid possible confusion. (`#3592 <https://github.com/pytest-dev/pytest/issues/3592>`_) + + +Trivial/Internal Changes +------------------------ + +- Remove obsolete ``__future__`` imports. (`#2319 <https://github.com/pytest-dev/pytest/issues/2319>`_) + +- Add CITATION to provide information on how to formally cite pytest. (`#3402 <https://github.com/pytest-dev/pytest/issues/3402>`_) + +- Replace broken type annotations with type comments. (`#3635 <https://github.com/pytest-dev/pytest/issues/3635>`_) + +- Pin ``pluggy`` to ``<0.8``. (`#3727 <https://github.com/pytest-dev/pytest/issues/3727>`_) + + +pytest 3.6.3 (2018-07-04) +========================= + +Bug Fixes +--------- + +- Fix ``ImportWarning`` triggered by explicit relative imports in + assertion-rewritten package modules. (`#3061 + <https://github.com/pytest-dev/pytest/issues/3061>`_) + +- Fix error in ``pytest.approx`` when dealing with 0-dimension numpy + arrays. (`#3593 <https://github.com/pytest-dev/pytest/issues/3593>`_) + +- No longer raise ``ValueError`` when using the ``get_marker`` API. (`#3605 + <https://github.com/pytest-dev/pytest/issues/3605>`_) + +- Fix problem where log messages with non-ascii characters would not + appear in the output log file. + (`#3630 <https://github.com/pytest-dev/pytest/issues/3630>`_) + +- No longer raise ``AttributeError`` when legacy marks can't be stored in + functions. (`#3631 <https://github.com/pytest-dev/pytest/issues/3631>`_) + + +Improved Documentation +---------------------- + +- The description above the example for ``@pytest.mark.skipif`` now better + matches the code. (`#3611 + <https://github.com/pytest-dev/pytest/issues/3611>`_) + + +Trivial/Internal Changes +------------------------ + +- Internal refactoring: removed unused ``CallSpec2tox ._globalid_args`` + attribute and ``metafunc`` parameter from ``CallSpec2.copy()``. (`#3598 + <https://github.com/pytest-dev/pytest/issues/3598>`_) + +- Silence usage of ``reduce`` warning in Python 2 (`#3609 + <https://github.com/pytest-dev/pytest/issues/3609>`_) + +- Fix usage of ``attr.ib`` deprecated ``convert`` parameter. (`#3653 + <https://github.com/pytest-dev/pytest/issues/3653>`_) + + +pytest 3.6.2 (2018-06-20) +========================= + +Bug Fixes +--------- + +- Fix regression in ``Node.add_marker`` by extracting the mark object of a + ``MarkDecorator``. (`#3555 + <https://github.com/pytest-dev/pytest/issues/3555>`_) + +- Warnings without ``location`` were reported as ``None``. This is corrected to + now report ``<undetermined location>``. (`#3563 + <https://github.com/pytest-dev/pytest/issues/3563>`_) + +- Continue to call finalizers in the stack when a finalizer in a former scope + raises an exception. (`#3569 + <https://github.com/pytest-dev/pytest/issues/3569>`_) + +- Fix encoding error with ``print`` statements in doctests (`#3583 + <https://github.com/pytest-dev/pytest/issues/3583>`_) + + +Improved Documentation +---------------------- + +- Add documentation for the ``--strict`` flag. (`#3549 + <https://github.com/pytest-dev/pytest/issues/3549>`_) + + +Trivial/Internal Changes +------------------------ + +- Update old quotation style to parens in fixture.rst documentation. (`#3525 + <https://github.com/pytest-dev/pytest/issues/3525>`_) + +- Improve display of hint about ``--fulltrace`` with ``KeyboardInterrupt``. + (`#3545 <https://github.com/pytest-dev/pytest/issues/3545>`_) + +- pytest's testsuite is no longer runnable through ``python setup.py test`` -- + instead invoke ``pytest`` or ``tox`` directly. (`#3552 + <https://github.com/pytest-dev/pytest/issues/3552>`_) + +- Fix typo in documentation (`#3567 + <https://github.com/pytest-dev/pytest/issues/3567>`_) + + +pytest 3.6.1 (2018-06-05) +========================= + +Bug Fixes +--------- + +- Fixed a bug where stdout and stderr were logged twice by junitxml when a test + was marked xfail. (`#3491 + <https://github.com/pytest-dev/pytest/issues/3491>`_) + +- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating + ``FixtureInfo``. (`#3498 + <https://github.com/pytest-dev/pytest/issues/3498>`_) + +- Fix assertion rewriter compatibility with libraries that monkey patch + ``file`` objects. (`#3503 + <https://github.com/pytest-dev/pytest/issues/3503>`_) + + +Improved Documentation +---------------------- + +- Added a section on how to use fixtures as factories to the fixture + documentation. (`#3461 <https://github.com/pytest-dev/pytest/issues/3461>`_) + + +Trivial/Internal Changes +------------------------ + +- Enable caching for pip/pre-commit in order to reduce build time on + travis/appveyor. (`#3502 + <https://github.com/pytest-dev/pytest/issues/3502>`_) + +- Switch pytest to the src/ layout as we already suggested it for good practice + - now we implement it as well. (`#3513 + <https://github.com/pytest-dev/pytest/issues/3513>`_) + +- Fix if in tests to support 3.7.0b5, where a docstring handling in AST got + reverted. (`#3530 <https://github.com/pytest-dev/pytest/issues/3530>`_) + +- Remove some python2.5 compatibility code. (`#3529 + <https://github.com/pytest-dev/pytest/issues/3529>`_) + + +pytest 3.6.0 (2018-05-23) +========================= + +Features +-------- + +- Revamp the internals of the ``pytest.mark`` implementation with correct per + node handling which fixes a number of long standing bugs caused by the old + design. This introduces new ``Node.iter_markers(name)`` and + ``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to + read the `reasons for the revamp in the docs + <https://docs.pytest.org/en/stable/historical-notes.html#marker-revamp-and-iteration>`_, + or jump over to details about `updating existing code to use the new APIs + <https://docs.pytest.org/en/stable/historical-notes.html#updating-code>`_. + (`#3317 <https://github.com/pytest-dev/pytest/issues/3317>`_) + +- Now when ``@pytest.fixture`` is applied more than once to the same function a + ``ValueError`` is raised. This buggy behavior would cause surprising problems + and if was working for a test suite it was mostly by accident. (`#2334 + <https://github.com/pytest-dev/pytest/issues/2334>`_) + +- Support for Python 3.7's builtin ``breakpoint()`` method, see `Using the + builtin breakpoint function + <https://docs.pytest.org/en/stable/usage.html#breakpoint-builtin>`_ for + details. (`#3180 <https://github.com/pytest-dev/pytest/issues/3180>`_) + +- ``monkeypatch`` now supports a ``context()`` function which acts as a context + manager which undoes all patching done within the ``with`` block. (`#3290 + <https://github.com/pytest-dev/pytest/issues/3290>`_) + +- The ``--pdb`` option now causes KeyboardInterrupt to enter the debugger, + instead of stopping the test session. On python 2.7, hitting CTRL+C again + exits the debugger. On python 3.2 and higher, use CTRL+D. (`#3299 + <https://github.com/pytest-dev/pytest/issues/3299>`_) + +- pytest no longer changes the log level of the root logger when the + ``log-level`` parameter has greater numeric value than that of the level of + the root logger, which makes it play better with custom logging configuration + in user code. (`#3307 <https://github.com/pytest-dev/pytest/issues/3307>`_) + + +Bug Fixes +--------- + +- A rare race-condition which might result in corrupted ``.pyc`` files on + Windows has been hopefully solved. (`#3008 + <https://github.com/pytest-dev/pytest/issues/3008>`_) + +- Also use iter_marker for discovering the marks applying for marker + expressions from the cli to avoid the bad data from the legacy mark storage. + (`#3441 <https://github.com/pytest-dev/pytest/issues/3441>`_) + +- When showing diffs of failed assertions where the contents contain only + whitespace, escape them using ``repr()`` first to make it easy to spot the + differences. (`#3443 <https://github.com/pytest-dev/pytest/issues/3443>`_) + + +Improved Documentation +---------------------- + +- Change documentation copyright year to a range which auto-updates itself each + time it is published. (`#3303 + <https://github.com/pytest-dev/pytest/issues/3303>`_) + + +Trivial/Internal Changes +------------------------ + +- ``pytest`` now depends on the `python-atomicwrites + <https://github.com/untitaker/python-atomicwrites>`_ library. (`#3008 + <https://github.com/pytest-dev/pytest/issues/3008>`_) + +- Update all pypi.python.org URLs to pypi.org. (`#3431 + <https://github.com/pytest-dev/pytest/issues/3431>`_) + +- Detect `pytest_` prefixed hooks using the internal plugin manager since + ``pluggy`` is deprecating the ``implprefix`` argument to ``PluginManager``. + (`#3487 <https://github.com/pytest-dev/pytest/issues/3487>`_) + +- Import ``Mapping`` and ``Sequence`` from ``_pytest.compat`` instead of + directly from ``collections`` in ``python_api.py::approx``. Add ``Mapping`` + to ``_pytest.compat``, import it from ``collections`` on python 2, but from + ``collections.abc`` on Python 3 to avoid a ``DeprecationWarning`` on Python + 3.7 or newer. (`#3497 <https://github.com/pytest-dev/pytest/issues/3497>`_) + + +pytest 3.5.1 (2018-04-23) +========================= + + +Bug Fixes +--------- + +- Reset ``sys.last_type``, ``sys.last_value`` and ``sys.last_traceback`` before + each test executes. Those attributes are added by pytest during the test run + to aid debugging, but were never reset so they would create a leaking + reference to the last failing test's frame which in turn could never be + reclaimed by the garbage collector. (`#2798 + <https://github.com/pytest-dev/pytest/issues/2798>`_) + +- ``pytest.raises`` now raises ``TypeError`` when receiving an unknown keyword + argument. (`#3348 <https://github.com/pytest-dev/pytest/issues/3348>`_) + +- ``pytest.raises`` now works with exception classes that look like iterables. + (`#3372 <https://github.com/pytest-dev/pytest/issues/3372>`_) + + +Improved Documentation +---------------------- + +- Fix typo in ``caplog`` fixture documentation, which incorrectly identified + certain attributes as methods. (`#3406 + <https://github.com/pytest-dev/pytest/issues/3406>`_) + + +Trivial/Internal Changes +------------------------ + +- Added a more indicative error message when parametrizing a function whose + argument takes a default value. (`#3221 + <https://github.com/pytest-dev/pytest/issues/3221>`_) + +- Remove internal ``_pytest.terminal.flatten`` function in favor of + ``more_itertools.collapse``. (`#3330 + <https://github.com/pytest-dev/pytest/issues/3330>`_) + +- Import some modules from ``collections.abc`` instead of ``collections`` as + the former modules trigger ``DeprecationWarning`` in Python 3.7. (`#3339 + <https://github.com/pytest-dev/pytest/issues/3339>`_) + +- record_property is no longer experimental, removing the warnings was + forgotten. (`#3360 <https://github.com/pytest-dev/pytest/issues/3360>`_) + +- Mention in documentation and CLI help that fixtures with leading ``_`` are + printed by ``pytest --fixtures`` only if the ``-v`` option is added. (`#3398 + <https://github.com/pytest-dev/pytest/issues/3398>`_) + + +pytest 3.5.0 (2018-03-21) +========================= + +Deprecations and Removals +------------------------- + +- ``record_xml_property`` fixture is now deprecated in favor of the more + generic ``record_property``. (`#2770 + <https://github.com/pytest-dev/pytest/issues/2770>`_) + +- Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py + files, because they "leak" to the entire directory tree. `See the docs <https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`_ for the rationale behind this decision (`#3084 + <https://github.com/pytest-dev/pytest/issues/3084>`_) + + +Features +-------- + +- New ``--show-capture`` command-line option that allows to specify how to + display captured output when tests fail: ``no``, ``stdout``, ``stderr``, + ``log`` or ``all`` (the default). (`#1478 + <https://github.com/pytest-dev/pytest/issues/1478>`_) + +- New ``--rootdir`` command-line option to override the rules for discovering + the root directory. See `customize + <https://docs.pytest.org/en/stable/customize.html>`_ in the documentation for + details. (`#1642 <https://github.com/pytest-dev/pytest/issues/1642>`_) + +- Fixtures are now instantiated based on their scopes, with higher-scoped + fixtures (such as ``session``) being instantiated first than lower-scoped + fixtures (such as ``function``). The relative order of fixtures of the same + scope is kept unchanged, based in their declaration order and their + dependencies. (`#2405 <https://github.com/pytest-dev/pytest/issues/2405>`_) + +- ``record_xml_property`` renamed to ``record_property`` and is now compatible + with xdist, markers and any reporter. ``record_xml_property`` name is now + deprecated. (`#2770 <https://github.com/pytest-dev/pytest/issues/2770>`_) + +- New ``--nf``, ``--new-first`` options: run new tests first followed by the + rest of the tests, in both cases tests are also sorted by the file modified + time, with more recent files coming first. (`#3034 + <https://github.com/pytest-dev/pytest/issues/3034>`_) + +- New ``--last-failed-no-failures`` command-line option that allows to specify + the behavior of the cache plugin's ```--last-failed`` feature when no tests + failed in the last run (or no cache was found): ``none`` or ``all`` (the + default). (`#3139 <https://github.com/pytest-dev/pytest/issues/3139>`_) + +- New ``--doctest-continue-on-failure`` command-line option to enable doctests + to show multiple failures for each snippet, instead of stopping at the first + failure. (`#3149 <https://github.com/pytest-dev/pytest/issues/3149>`_) + +- Captured log messages are added to the ``<system-out>`` tag in the generated + junit xml file if the ``junit_logging`` ini option is set to ``system-out``. + If the value of this ini option is ``system-err``, the logs are written to + ``<system-err>``. The default value for ``junit_logging`` is ``no``, meaning + captured logs are not written to the output file. (`#3156 + <https://github.com/pytest-dev/pytest/issues/3156>`_) + +- Allow the logging plugin to handle ``pytest_runtest_logstart`` and + ``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#3189 + <https://github.com/pytest-dev/pytest/issues/3189>`_) + +- Passing ``--log-cli-level`` in the command-line now automatically activates + live logging. (`#3190 <https://github.com/pytest-dev/pytest/issues/3190>`_) + +- Add command line option ``--deselect`` to allow deselection of individual + tests at collection time. (`#3198 + <https://github.com/pytest-dev/pytest/issues/3198>`_) + +- Captured logs are printed before entering pdb. (`#3204 + <https://github.com/pytest-dev/pytest/issues/3204>`_) + +- Deselected item count is now shown before tests are run, e.g. ``collected X + items / Y deselected``. (`#3213 + <https://github.com/pytest-dev/pytest/issues/3213>`_) + +- The builtin module ``platform`` is now available for use in expressions in + ``pytest.mark``. (`#3236 + <https://github.com/pytest-dev/pytest/issues/3236>`_) + +- The *short test summary info* section now is displayed after tracebacks and + warnings in the terminal. (`#3255 + <https://github.com/pytest-dev/pytest/issues/3255>`_) + +- New ``--verbosity`` flag to set verbosity level explicitly. (`#3296 + <https://github.com/pytest-dev/pytest/issues/3296>`_) + +- ``pytest.approx`` now accepts comparing a numpy array with a scalar. (`#3312 + <https://github.com/pytest-dev/pytest/issues/3312>`_) + + +Bug Fixes +--------- + +- Suppress ``IOError`` when closing the temporary file used for capturing + streams in Python 2.7. (`#2370 + <https://github.com/pytest-dev/pytest/issues/2370>`_) + +- Fixed ``clear()`` method on ``caplog`` fixture which cleared ``records``, but + not the ``text`` property. (`#3297 + <https://github.com/pytest-dev/pytest/issues/3297>`_) + +- During test collection, when stdin is not allowed to be read, the + ``DontReadFromStdin`` object still allow itself to be iterable and resolved + to an iterator without crashing. (`#3314 + <https://github.com/pytest-dev/pytest/issues/3314>`_) + + +Improved Documentation +---------------------- + +- Added a `reference <https://docs.pytest.org/en/stable/reference.html>`_ page + to the docs. (`#1713 <https://github.com/pytest-dev/pytest/issues/1713>`_) + + +Trivial/Internal Changes +------------------------ + +- Change minimum requirement of ``attrs`` to ``17.4.0``. (`#3228 + <https://github.com/pytest-dev/pytest/issues/3228>`_) + +- Renamed example directories so all tests pass when ran from the base + directory. (`#3245 <https://github.com/pytest-dev/pytest/issues/3245>`_) + +- Internal ``mark.py`` module has been turned into a package. (`#3250 + <https://github.com/pytest-dev/pytest/issues/3250>`_) + +- ``pytest`` now depends on the `more-itertools + <https://github.com/erikrose/more-itertools>`_ package. (`#3265 + <https://github.com/pytest-dev/pytest/issues/3265>`_) + +- Added warning when ``[pytest]`` section is used in a ``.cfg`` file passed + with ``-c`` (`#3268 <https://github.com/pytest-dev/pytest/issues/3268>`_) + +- ``nodeids`` can now be passed explicitly to ``FSCollector`` and ``Node`` + constructors. (`#3291 <https://github.com/pytest-dev/pytest/issues/3291>`_) + +- Internal refactoring of ``FormattedExcinfo`` to use ``attrs`` facilities and + remove old support code for legacy Python versions. (`#3292 + <https://github.com/pytest-dev/pytest/issues/3292>`_) + +- Refactoring to unify how verbosity is handled internally. (`#3296 + <https://github.com/pytest-dev/pytest/issues/3296>`_) + +- Internal refactoring to better integrate with argparse. (`#3304 + <https://github.com/pytest-dev/pytest/issues/3304>`_) + +- Fix a python example when calling a fixture in doc/en/usage.rst (`#3308 + <https://github.com/pytest-dev/pytest/issues/3308>`_) + + +pytest 3.4.2 (2018-03-04) +========================= + +Bug Fixes +--------- + +- Removed progress information when capture option is ``no``. (`#3203 + <https://github.com/pytest-dev/pytest/issues/3203>`_) + +- Refactor check of bindir from ``exists`` to ``isdir``. (`#3241 + <https://github.com/pytest-dev/pytest/issues/3241>`_) + +- Fix ``TypeError`` issue when using ``approx`` with a ``Decimal`` value. + (`#3247 <https://github.com/pytest-dev/pytest/issues/3247>`_) + +- Fix reference cycle generated when using the ``request`` fixture. (`#3249 + <https://github.com/pytest-dev/pytest/issues/3249>`_) + +- ``[tool:pytest]`` sections in ``*.cfg`` files passed by the ``-c`` option are + now properly recognized. (`#3260 + <https://github.com/pytest-dev/pytest/issues/3260>`_) + + +Improved Documentation +---------------------- + +- Add logging plugin to plugins list. (`#3209 + <https://github.com/pytest-dev/pytest/issues/3209>`_) + + +Trivial/Internal Changes +------------------------ + +- Fix minor typo in fixture.rst (`#3259 + <https://github.com/pytest-dev/pytest/issues/3259>`_) + + +pytest 3.4.1 (2018-02-20) +========================= + +Bug Fixes +--------- + +- Move import of ``doctest.UnexpectedException`` to top-level to avoid possible + errors when using ``--pdb``. (`#1810 + <https://github.com/pytest-dev/pytest/issues/1810>`_) + +- Added printing of captured stdout/stderr before entering pdb, and improved a + test which was giving false negatives about output capturing. (`#3052 + <https://github.com/pytest-dev/pytest/issues/3052>`_) + +- Fix ordering of tests using parametrized fixtures which can lead to fixtures + being created more than necessary. (`#3161 + <https://github.com/pytest-dev/pytest/issues/3161>`_) + +- Fix bug where logging happening at hooks outside of "test run" hooks would + cause an internal error. (`#3184 + <https://github.com/pytest-dev/pytest/issues/3184>`_) + +- Detect arguments injected by ``unittest.mock.patch`` decorator correctly when + pypi ``mock.patch`` is installed and imported. (`#3206 + <https://github.com/pytest-dev/pytest/issues/3206>`_) + +- Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner + on what happened: When no exception was raised, the "matching '...'" part got + removed as it falsely implies that an exception was raised but it didn't + match. When a wrong exception was raised, it's now thrown (like + ``pytest.raised()`` without ``match=`` would) instead of complaining about + the unmatched text. (`#3222 + <https://github.com/pytest-dev/pytest/issues/3222>`_) + +- Fixed output capture handling in doctests on macOS. (`#985 + <https://github.com/pytest-dev/pytest/issues/985>`_) + + +Improved Documentation +---------------------- + +- Add Sphinx parameter docs for ``match`` and ``message`` args to + ``pytest.raises``. (`#3202 + <https://github.com/pytest-dev/pytest/issues/3202>`_) + + +Trivial/Internal Changes +------------------------ + +- pytest has changed the publication procedure and is now being published to + PyPI directly from Travis. (`#3060 + <https://github.com/pytest-dev/pytest/issues/3060>`_) + +- Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in + order to comply with the naming convention. (`#3166 + <https://github.com/pytest-dev/pytest/issues/3166>`_) + +- Skip failing pdb/doctest test on mac. (`#985 + <https://github.com/pytest-dev/pytest/issues/985>`_) + + +pytest 3.4.0 (2018-01-30) +========================= + +Deprecations and Removals +------------------------- + +- All pytest classes now subclass ``object`` for better Python 2/3 compatibility. + This should not affect user code except in very rare edge cases. (`#2147 + <https://github.com/pytest-dev/pytest/issues/2147>`_) + + +Features +-------- + +- Introduce ``empty_parameter_set_mark`` ini option to select which mark to + apply when ``@pytest.mark.parametrize`` is given an empty set of parameters. + Valid options are ``skip`` (default) and ``xfail``. Note that it is planned + to change the default to ``xfail`` in future releases as this is considered + less error prone. (`#2527 + <https://github.com/pytest-dev/pytest/issues/2527>`_) + +- **Incompatible change**: after community feedback the `logging + <https://docs.pytest.org/en/stable/logging.html>`_ functionality has + undergone some changes. Please consult the `logging documentation + <https://docs.pytest.org/en/stable/logging.html#incompatible-changes-in-pytest-3-4>`_ + for details. (`#3013 <https://github.com/pytest-dev/pytest/issues/3013>`_) + +- Console output falls back to "classic" mode when capturing is disabled (``-s``), + otherwise the output gets garbled to the point of being useless. (`#3038 + <https://github.com/pytest-dev/pytest/issues/3038>`_) + +- New `pytest_runtest_logfinish + <https://docs.pytest.org/en/stable/reference.html#_pytest.hookspec.pytest_runtest_logfinish>`_ + hook which is called when a test item has finished executing, analogous to + `pytest_runtest_logstart + <https://docs.pytest.org/en/stable/reference.html#_pytest.hookspec.pytest_runtest_logstart>`_. + (`#3101 <https://github.com/pytest-dev/pytest/issues/3101>`_) + +- Improve performance when collecting tests using many fixtures. (`#3107 + <https://github.com/pytest-dev/pytest/issues/3107>`_) + +- New ``caplog.get_records(when)`` method which provides access to the captured + records for the ``"setup"``, ``"call"`` and ``"teardown"`` + testing stages. (`#3117 <https://github.com/pytest-dev/pytest/issues/3117>`_) + +- New fixture ``record_xml_attribute`` that allows modifying and inserting + attributes on the ``<testcase>`` xml node in JUnit reports. (`#3130 + <https://github.com/pytest-dev/pytest/issues/3130>`_) + +- The default cache directory has been renamed from ``.cache`` to + ``.pytest_cache`` after community feedback that the name ``.cache`` did not + make it clear that it was used by pytest. (`#3138 + <https://github.com/pytest-dev/pytest/issues/3138>`_) + +- Colorize the levelname column in the live-log output. (`#3142 + <https://github.com/pytest-dev/pytest/issues/3142>`_) + + +Bug Fixes +--------- + +- Fix hanging pexpect test on MacOS by using flush() instead of wait(). + (`#2022 <https://github.com/pytest-dev/pytest/issues/2022>`_) + +- Fix restoring Python state after in-process pytest runs with the + ``pytester`` plugin; this may break tests using multiple inprocess + pytest runs if later ones depend on earlier ones leaking global interpreter + changes. (`#3016 <https://github.com/pytest-dev/pytest/issues/3016>`_) + +- Fix skipping plugin reporting hook when test aborted before plugin setup + hook. (`#3074 <https://github.com/pytest-dev/pytest/issues/3074>`_) + +- Fix progress percentage reported when tests fail during teardown. (`#3088 + <https://github.com/pytest-dev/pytest/issues/3088>`_) + +- **Incompatible change**: ``-o/--override`` option no longer eats all the + remaining options, which can lead to surprising behavior: for example, + ``pytest -o foo=1 /path/to/test.py`` would fail because ``/path/to/test.py`` + would be considered as part of the ``-o`` command-line argument. One + consequence of this is that now multiple configuration overrides need + multiple ``-o`` flags: ``pytest -o foo=1 -o bar=2``. (`#3103 + <https://github.com/pytest-dev/pytest/issues/3103>`_) + + +Improved Documentation +---------------------- + +- Document hooks (defined with ``historic=True``) which cannot be used with + ``hookwrapper=True``. (`#2423 + <https://github.com/pytest-dev/pytest/issues/2423>`_) + +- Clarify that warning capturing doesn't change the warning filter by default. + (`#2457 <https://github.com/pytest-dev/pytest/issues/2457>`_) + +- Clarify a possible confusion when using pytest_fixture_setup with fixture + functions that return None. (`#2698 + <https://github.com/pytest-dev/pytest/issues/2698>`_) + +- Fix the wording of a sentence on doctest flags used in pytest. (`#3076 + <https://github.com/pytest-dev/pytest/issues/3076>`_) + +- Prefer ``https://*.readthedocs.io`` over ``http://*.rtfd.org`` for links in + the documentation. (`#3092 + <https://github.com/pytest-dev/pytest/issues/3092>`_) + +- Improve readability (wording, grammar) of Getting Started guide (`#3131 + <https://github.com/pytest-dev/pytest/issues/3131>`_) + +- Added note that calling pytest.main multiple times from the same process is + not recommended because of import caching. (`#3143 + <https://github.com/pytest-dev/pytest/issues/3143>`_) + + +Trivial/Internal Changes +------------------------ + +- Show a simple and easy error when keyword expressions trigger a syntax error + (for example, ``"-k foo and import"`` will show an error that you can not use + the ``import`` keyword in expressions). (`#2953 + <https://github.com/pytest-dev/pytest/issues/2953>`_) + +- Change parametrized automatic test id generation to use the ``__name__`` + attribute of functions instead of the fallback argument name plus counter. + (`#2976 <https://github.com/pytest-dev/pytest/issues/2976>`_) + +- Replace py.std with stdlib imports. (`#3067 + <https://github.com/pytest-dev/pytest/issues/3067>`_) + +- Corrected 'you' to 'your' in logging docs. (`#3129 + <https://github.com/pytest-dev/pytest/issues/3129>`_) + + +pytest 3.3.2 (2017-12-25) +========================= + +Bug Fixes +--------- + +- pytester: ignore files used to obtain current user metadata in the fd leak + detector. (`#2784 <https://github.com/pytest-dev/pytest/issues/2784>`_) + +- Fix **memory leak** where objects returned by fixtures were never destructed + by the garbage collector. (`#2981 + <https://github.com/pytest-dev/pytest/issues/2981>`_) + +- Fix conversion of pyargs to filename to not convert symlinks on Python 2. (`#2985 + <https://github.com/pytest-dev/pytest/issues/2985>`_) + +- ``PYTEST_DONT_REWRITE`` is now checked for plugins too rather than only for + test modules. (`#2995 <https://github.com/pytest-dev/pytest/issues/2995>`_) + + +Improved Documentation +---------------------- + +- Add clarifying note about behavior of multiple parametrized arguments (`#3001 + <https://github.com/pytest-dev/pytest/issues/3001>`_) + + +Trivial/Internal Changes +------------------------ + +- Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_, + `#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_) + +- Clean up code by replacing imports and references of ``_ast`` to ``ast``. + (`#3018 <https://github.com/pytest-dev/pytest/issues/3018>`_) + + +pytest 3.3.1 (2017-12-05) +========================= + +Bug Fixes +--------- + +- Fix issue about ``-p no:<plugin>`` having no effect. (`#2920 + <https://github.com/pytest-dev/pytest/issues/2920>`_) + +- Fix regression with warnings that contained non-strings in their arguments in + Python 2. (`#2956 <https://github.com/pytest-dev/pytest/issues/2956>`_) + +- Always escape null bytes when setting ``PYTEST_CURRENT_TEST``. (`#2957 + <https://github.com/pytest-dev/pytest/issues/2957>`_) + +- Fix ``ZeroDivisionError`` when using the ``testmon`` plugin when no tests + were actually collected. (`#2971 + <https://github.com/pytest-dev/pytest/issues/2971>`_) + +- Bring back ``TerminalReporter.writer`` as an alias to + ``TerminalReporter._tw``. This alias was removed by accident in the ``3.3.0`` + release. (`#2984 <https://github.com/pytest-dev/pytest/issues/2984>`_) + +- The ``pytest-capturelog`` plugin is now also blacklisted, avoiding errors when + running pytest with it still installed. (`#3004 + <https://github.com/pytest-dev/pytest/issues/3004>`_) + + +Improved Documentation +---------------------- + +- Fix broken link to plugin ``pytest-localserver``. (`#2963 + <https://github.com/pytest-dev/pytest/issues/2963>`_) + + +Trivial/Internal Changes +------------------------ + +- Update github "bugs" link in ``CONTRIBUTING.rst`` (`#2949 + <https://github.com/pytest-dev/pytest/issues/2949>`_) + + +pytest 3.3.0 (2017-11-23) +========================= + +Deprecations and Removals +------------------------- + +- pytest no longer supports Python **2.6** and **3.3**. Those Python versions + are EOL for some time now and incur maintenance and compatibility costs on + the pytest core team, and following up with the rest of the community we + decided that they will no longer be supported starting on this version. Users + which still require those versions should pin pytest to ``<3.3``. (`#2812 + <https://github.com/pytest-dev/pytest/issues/2812>`_) + +- Remove internal ``_preloadplugins()`` function. This removal is part of the + ``pytest_namespace()`` hook deprecation. (`#2636 + <https://github.com/pytest-dev/pytest/issues/2636>`_) + +- Internally change ``CallSpec2`` to have a list of marks instead of a broken + mapping of keywords. This removes the keywords attribute of the internal + ``CallSpec2`` class. (`#2672 + <https://github.com/pytest-dev/pytest/issues/2672>`_) + +- Remove ParameterSet.deprecated_arg_dict - its not a public api and the lack + of the underscore was a naming error. (`#2675 + <https://github.com/pytest-dev/pytest/issues/2675>`_) + +- Remove the internal multi-typed attribute ``Node._evalskip`` and replace it + with the boolean ``Node._skipped_by_mark``. (`#2767 + <https://github.com/pytest-dev/pytest/issues/2767>`_) + +- The ``params`` list passed to ``pytest.fixture`` is now for + all effects considered immutable and frozen at the moment of the ``pytest.fixture`` + call. Previously the list could be changed before the first invocation of the fixture + allowing for a form of dynamic parametrization (for example, updated from command-line options), + but this was an unwanted implementation detail which complicated the internals and prevented + some internal cleanup. See issue `#2959 <https://github.com/pytest-dev/pytest/issues/2959>`_ + for details and a recommended workaround. + +Features +-------- + +- ``pytest_fixture_post_finalizer`` hook can now receive a ``request`` + argument. (`#2124 <https://github.com/pytest-dev/pytest/issues/2124>`_) + +- Replace the old introspection code in compat.py that determines the available + arguments of fixtures with inspect.signature on Python 3 and + funcsigs.signature on Python 2. This should respect ``__signature__`` + declarations on functions. (`#2267 + <https://github.com/pytest-dev/pytest/issues/2267>`_) + +- Report tests with global ``pytestmark`` variable only once. (`#2549 + <https://github.com/pytest-dev/pytest/issues/2549>`_) + +- Now pytest displays the total progress percentage while running tests. The + previous output style can be set by configuring the ``console_output_style`` + setting to ``classic``. (`#2657 <https://github.com/pytest-dev/pytest/issues/2657>`_) + +- Match ``warns`` signature to ``raises`` by adding ``match`` keyword. (`#2708 + <https://github.com/pytest-dev/pytest/issues/2708>`_) + +- pytest now captures and displays output from the standard ``logging`` module. + The user can control the logging level to be captured by specifying options + in ``pytest.ini``, the command line and also during individual tests using + markers. Also, a ``caplog`` fixture is available that enables users to test + the captured log during specific tests (similar to ``capsys`` for example). + For more information, please see the `logging docs + <https://docs.pytest.org/en/stable/logging.html>`_. This feature was + introduced by merging the popular `pytest-catchlog + <https://pypi.org/project/pytest-catchlog/>`_ plugin, thanks to `Thomas Hisch + <https://github.com/thisch>`_. Be advised that during the merging the + backward compatibility interface with the defunct ``pytest-capturelog`` has + been dropped. (`#2794 <https://github.com/pytest-dev/pytest/issues/2794>`_) + +- Add ``allow_module_level`` kwarg to ``pytest.skip()``, enabling to skip the + whole module. (`#2808 <https://github.com/pytest-dev/pytest/issues/2808>`_) + +- Allow setting ``file_or_dir``, ``-c``, and ``-o`` in PYTEST_ADDOPTS. (`#2824 + <https://github.com/pytest-dev/pytest/issues/2824>`_) + +- Return stdout/stderr capture results as a ``namedtuple``, so ``out`` and + ``err`` can be accessed by attribute. (`#2879 + <https://github.com/pytest-dev/pytest/issues/2879>`_) + +- Add ``capfdbinary``, a version of ``capfd`` which returns bytes from + ``readouterr()``. (`#2923 + <https://github.com/pytest-dev/pytest/issues/2923>`_) + +- Add ``capsysbinary`` a version of ``capsys`` which returns bytes from + ``readouterr()``. (`#2934 + <https://github.com/pytest-dev/pytest/issues/2934>`_) + +- Implement feature to skip ``setup.py`` files when run with + ``--doctest-modules``. (`#502 + <https://github.com/pytest-dev/pytest/issues/502>`_) + + +Bug Fixes +--------- + +- Resume output capturing after ``capsys/capfd.disabled()`` context manager. + (`#1993 <https://github.com/pytest-dev/pytest/issues/1993>`_) + +- ``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hooks are now + called for all ``conftest.py`` files. (`#2124 + <https://github.com/pytest-dev/pytest/issues/2124>`_) + +- If an exception happens while loading a plugin, pytest no longer hides the + original traceback. In Python 2 it will show the original traceback with a new + message that explains in which plugin. In Python 3 it will show 2 canonized + exceptions, the original exception while loading the plugin in addition to an + exception that pytest throws about loading a plugin. (`#2491 + <https://github.com/pytest-dev/pytest/issues/2491>`_) + +- ``capsys`` and ``capfd`` can now be used by other fixtures. (`#2709 + <https://github.com/pytest-dev/pytest/issues/2709>`_) + +- Internal ``pytester`` plugin properly encodes ``bytes`` arguments to + ``utf-8``. (`#2738 <https://github.com/pytest-dev/pytest/issues/2738>`_) + +- ``testdir`` now uses use the same method used by ``tmpdir`` to create its + temporary directory. This changes the final structure of the ``testdir`` + directory slightly, but should not affect usage in normal scenarios and + avoids a number of potential problems. (`#2751 + <https://github.com/pytest-dev/pytest/issues/2751>`_) + +- pytest no longer complains about warnings with unicode messages being + non-ascii compatible even for ascii-compatible messages. As a result of this, + warnings with unicode messages are converted first to an ascii representation + for safety. (`#2809 <https://github.com/pytest-dev/pytest/issues/2809>`_) + +- Change return value of pytest command when ``--maxfail`` is reached from + ``2`` (interrupted) to ``1`` (failed). (`#2845 + <https://github.com/pytest-dev/pytest/issues/2845>`_) + +- Fix issue in assertion rewriting which could lead it to rewrite modules which + should not be rewritten. (`#2939 + <https://github.com/pytest-dev/pytest/issues/2939>`_) + +- Handle marks without description in ``pytest.ini``. (`#2942 + <https://github.com/pytest-dev/pytest/issues/2942>`_) + + +Trivial/Internal Changes +------------------------ + +- pytest now depends on `attrs <https://pypi.org/project/attrs/>`__ for internal + structures to ease code maintainability. (`#2641 + <https://github.com/pytest-dev/pytest/issues/2641>`_) + +- Refactored internal Python 2/3 compatibility code to use ``six``. (`#2642 + <https://github.com/pytest-dev/pytest/issues/2642>`_) + +- Stop vendoring ``pluggy`` - we're missing out on its latest changes for not + much benefit (`#2719 <https://github.com/pytest-dev/pytest/issues/2719>`_) + +- Internal refactor: simplify ascii string escaping by using the + backslashreplace error handler in newer Python 3 versions. (`#2734 + <https://github.com/pytest-dev/pytest/issues/2734>`_) + +- Remove unnecessary mark evaluator in unittest plugin (`#2767 + <https://github.com/pytest-dev/pytest/issues/2767>`_) + +- Calls to ``Metafunc.addcall`` now emit a deprecation warning. This function + is scheduled to be removed in ``pytest-4.0``. (`#2876 + <https://github.com/pytest-dev/pytest/issues/2876>`_) + +- Internal move of the parameterset extraction to a more maintainable place. + (`#2877 <https://github.com/pytest-dev/pytest/issues/2877>`_) + +- Internal refactoring to simplify scope node lookup. (`#2910 + <https://github.com/pytest-dev/pytest/issues/2910>`_) + +- Configure ``pytest`` to prevent pip from installing pytest in unsupported + Python versions. (`#2922 + <https://github.com/pytest-dev/pytest/issues/2922>`_) + + +pytest 3.2.5 (2017-11-15) +========================= + +Bug Fixes +--------- + +- Remove ``py<1.5`` restriction from ``pytest`` as this can cause version + conflicts in some installations. (`#2926 + <https://github.com/pytest-dev/pytest/issues/2926>`_) + + +pytest 3.2.4 (2017-11-13) +========================= + +Bug Fixes +--------- + +- Fix the bug where running with ``--pyargs`` will result in items with + empty ``parent.nodeid`` if run from a different root directory. (`#2775 + <https://github.com/pytest-dev/pytest/issues/2775>`_) + +- Fix issue with ``@pytest.parametrize`` if argnames was specified as keyword arguments. + (`#2819 <https://github.com/pytest-dev/pytest/issues/2819>`_) + +- Strip whitespace from marker names when reading them from INI config. (`#2856 + <https://github.com/pytest-dev/pytest/issues/2856>`_) + +- Show full context of doctest source in the pytest output, if the line number of + failed example in the docstring is < 9. (`#2882 + <https://github.com/pytest-dev/pytest/issues/2882>`_) + +- Match fixture paths against actual path segments in order to avoid matching folders which share a prefix. + (`#2836 <https://github.com/pytest-dev/pytest/issues/2836>`_) + +Improved Documentation +---------------------- + +- Introduce a dedicated section about conftest.py. (`#1505 + <https://github.com/pytest-dev/pytest/issues/1505>`_) + +- Explicitly mention ``xpass`` in the documentation of ``xfail``. (`#1997 + <https://github.com/pytest-dev/pytest/issues/1997>`_) + +- Append example for pytest.param in the example/parametrize document. (`#2658 + <https://github.com/pytest-dev/pytest/issues/2658>`_) + +- Clarify language of proposal for fixtures parameters (`#2893 + <https://github.com/pytest-dev/pytest/issues/2893>`_) + +- List python 3.6 in the documented supported versions in the getting started + document. (`#2903 <https://github.com/pytest-dev/pytest/issues/2903>`_) + +- Clarify the documentation of available fixture scopes. (`#538 + <https://github.com/pytest-dev/pytest/issues/538>`_) + +- Add documentation about the ``python -m pytest`` invocation adding the + current directory to sys.path. (`#911 + <https://github.com/pytest-dev/pytest/issues/911>`_) + + +pytest 3.2.3 (2017-10-03) +========================= + +Bug Fixes +--------- + +- Fix crash in tab completion when no prefix is given. (`#2748 + <https://github.com/pytest-dev/pytest/issues/2748>`_) + +- The equality checking function (``__eq__``) of ``MarkDecorator`` returns + ``False`` if one object is not an instance of ``MarkDecorator``. (`#2758 + <https://github.com/pytest-dev/pytest/issues/2758>`_) + +- When running ``pytest --fixtures-per-test``: don't crash if an item has no + _fixtureinfo attribute (e.g. doctests) (`#2788 + <https://github.com/pytest-dev/pytest/issues/2788>`_) + + +Improved Documentation +---------------------- + +- In help text of ``-k`` option, add example of using ``not`` to not select + certain tests whose names match the provided expression. (`#1442 + <https://github.com/pytest-dev/pytest/issues/1442>`_) + +- Add note in ``parametrize.rst`` about calling ``metafunc.parametrize`` + multiple times. (`#1548 <https://github.com/pytest-dev/pytest/issues/1548>`_) + + +Trivial/Internal Changes +------------------------ + +- Set ``xfail_strict=True`` in pytest's own test suite to catch expected + failures as soon as they start to pass. (`#2722 + <https://github.com/pytest-dev/pytest/issues/2722>`_) + +- Fix typo in example of passing a callable to markers (in example/markers.rst) + (`#2765 <https://github.com/pytest-dev/pytest/issues/2765>`_) + + +pytest 3.2.2 (2017-09-06) +========================= + +Bug Fixes +--------- + +- Calling the deprecated ``request.getfuncargvalue()`` now shows the source of + the call. (`#2681 <https://github.com/pytest-dev/pytest/issues/2681>`_) + +- Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699 + <https://github.com/pytest-dev/pytest/issues/2699>`_) + +- Fixed edge-case during collection: attributes which raised ``pytest.fail`` + when accessed would abort the entire collection. (`#2707 + <https://github.com/pytest-dev/pytest/issues/2707>`_) + +- Fix ``ReprFuncArgs`` with mixed unicode and UTF-8 args. (`#2731 + <https://github.com/pytest-dev/pytest/issues/2731>`_) + + +Improved Documentation +---------------------- + +- In examples on working with custom markers, add examples demonstrating the + usage of ``pytest.mark.MARKER_NAME.with_args`` in comparison with + ``pytest.mark.MARKER_NAME.__call__`` (`#2604 + <https://github.com/pytest-dev/pytest/issues/2604>`_) + +- In one of the simple examples, use ``pytest_collection_modifyitems()`` to skip + tests based on a command-line option, allowing its sharing while preventing a + user error when acessing ``pytest.config`` before the argument parsing. + (`#2653 <https://github.com/pytest-dev/pytest/issues/2653>`_) + + +Trivial/Internal Changes +------------------------ + +- Fixed minor error in 'Good Practices/Manual Integration' code snippet. + (`#2691 <https://github.com/pytest-dev/pytest/issues/2691>`_) + +- Fixed typo in goodpractices.rst. (`#2721 + <https://github.com/pytest-dev/pytest/issues/2721>`_) + +- Improve user guidance regarding ``--resultlog`` deprecation. (`#2739 + <https://github.com/pytest-dev/pytest/issues/2739>`_) + + +pytest 3.2.1 (2017-08-08) +========================= + +Bug Fixes +--------- + +- Fixed small terminal glitch when collecting a single test item. (`#2579 + <https://github.com/pytest-dev/pytest/issues/2579>`_) + +- Correctly consider ``/`` as the file separator to automatically mark plugin + files for rewrite on Windows. (`#2591 <https://github.com/pytest- + dev/pytest/issues/2591>`_) + +- Properly escape test names when setting ``PYTEST_CURRENT_TEST`` environment + variable. (`#2644 <https://github.com/pytest-dev/pytest/issues/2644>`_) + +- Fix error on Windows and Python 3.6+ when ``sys.stdout`` has been replaced + with a stream-like object which does not implement the full ``io`` module + buffer protocol. In particular this affects ``pytest-xdist`` users on the + aforementioned platform. (`#2666 <https://github.com/pytest- + dev/pytest/issues/2666>`_) + + +Improved Documentation +---------------------- + +- Explicitly document which pytest features work with ``unittest``. (`#2626 + <https://github.com/pytest-dev/pytest/issues/2626>`_) + + +pytest 3.2.0 (2017-07-30) +========================= + +Deprecations and Removals +------------------------- + +- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=`` + operators to avoid surprising/inconsistent behavior. See `the approx docs + <https://docs.pytest.org/en/stable/reference.html#pytest-approx>`_ for more + information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_) + +- All old-style specific behavior in current classes in the pytest's API is + considered deprecated at this point and will be removed in a future release. + This affects Python 2 users only and in rare situations. (`#2147 + <https://github.com/pytest-dev/pytest/issues/2147>`_) + +- A deprecation warning is now raised when using marks for parameters + in ``pytest.mark.parametrize``. Use ``pytest.param`` to apply marks to + parameters instead. (`#2427 <https://github.com/pytest-dev/pytest/issues/2427>`_) + + +Features +-------- + +- Add support for numpy arrays (and dicts) to approx. (`#1994 + <https://github.com/pytest-dev/pytest/issues/1994>`_) + +- Now test function objects have a ``pytestmark`` attribute containing a list + of marks applied directly to the test function, as opposed to marks inherited + from parent classes or modules. (`#2516 <https://github.com/pytest- + dev/pytest/issues/2516>`_) + +- Collection ignores local virtualenvs by default; ``--collect-in-virtualenv`` + overrides this behavior. (`#2518 <https://github.com/pytest- + dev/pytest/issues/2518>`_) + +- Allow class methods decorated as ``@staticmethod`` to be candidates for + collection as a test function. (Only for Python 2.7 and above. Python 2.6 + will still ignore static methods.) (`#2528 <https://github.com/pytest- + dev/pytest/issues/2528>`_) + +- Introduce ``mark.with_args`` in order to allow passing functions/classes as + sole argument to marks. (`#2540 <https://github.com/pytest- + dev/pytest/issues/2540>`_) + +- New ``cache_dir`` ini option: sets the directory where the contents of the + cache plugin are stored. Directory may be relative or absolute path: if relative path, then + directory is created relative to ``rootdir``, otherwise it is used as is. + Additionally path may contain environment variables which are expanded during + runtime. (`#2543 <https://github.com/pytest-dev/pytest/issues/2543>`_) + +- Introduce the ``PYTEST_CURRENT_TEST`` environment variable that is set with + the ``nodeid`` and stage (``setup``, ``call`` and ``teardown``) of the test + being currently executed. See the `documentation + <https://docs.pytest.org/en/stable/example/simple.html#pytest-current-test- + environment-variable>`_ for more info. (`#2583 <https://github.com/pytest- + dev/pytest/issues/2583>`_) + +- Introduced ``@pytest.mark.filterwarnings`` mark which allows overwriting the + warnings filter on a per test, class or module level. See the `docs + <https://docs.pytest.org/en/stable/warnings.html#pytest-mark- + filterwarnings>`_ for more information. (`#2598 <https://github.com/pytest- + dev/pytest/issues/2598>`_) + +- ``--last-failed`` now remembers forever when a test has failed and only + forgets it if it passes again. This makes it easy to fix a test suite by + selectively running files and fixing tests incrementally. (`#2621 + <https://github.com/pytest-dev/pytest/issues/2621>`_) + +- New ``pytest_report_collectionfinish`` hook which allows plugins to add + messages to the terminal reporting after collection has been finished + successfully. (`#2622 <https://github.com/pytest-dev/pytest/issues/2622>`_) + +- Added support for `PEP-415's <https://www.python.org/dev/peps/pep-0415/>`_ + ``Exception.__suppress_context__``. Now if a ``raise exception from None`` is + caught by pytest, pytest will no longer chain the context in the test report. + The behavior now matches Python's traceback behavior. (`#2631 + <https://github.com/pytest-dev/pytest/issues/2631>`_) + +- Exceptions raised by ``pytest.fail``, ``pytest.skip`` and ``pytest.xfail`` + now subclass BaseException, making them harder to be caught unintentionally + by normal code. (`#580 <https://github.com/pytest-dev/pytest/issues/580>`_) + + +Bug Fixes +--------- + +- Set ``stdin`` to a closed ``PIPE`` in ``pytester.py.Testdir.popen()`` for + avoid unwanted interactive ``pdb`` (`#2023 <https://github.com/pytest- + dev/pytest/issues/2023>`_) + +- Add missing ``encoding`` attribute to ``sys.std*`` streams when using + ``capsys`` capture mode. (`#2375 <https://github.com/pytest- + dev/pytest/issues/2375>`_) + +- Fix terminal color changing to black on Windows if ``colorama`` is imported + in a ``conftest.py`` file. (`#2510 <https://github.com/pytest- + dev/pytest/issues/2510>`_) + +- Fix line number when reporting summary of skipped tests. (`#2548 + <https://github.com/pytest-dev/pytest/issues/2548>`_) + +- capture: ensure that EncodedFile.name is a string. (`#2555 + <https://github.com/pytest-dev/pytest/issues/2555>`_) + +- The options ``--fixtures`` and ``--fixtures-per-test`` will now keep + indentation within docstrings. (`#2574 <https://github.com/pytest- + dev/pytest/issues/2574>`_) + +- doctests line numbers are now reported correctly, fixing `pytest-sugar#122 + <https://github.com/Frozenball/pytest-sugar/issues/122>`_. (`#2610 + <https://github.com/pytest-dev/pytest/issues/2610>`_) + +- Fix non-determinism in order of fixture collection. Adds new dependency + (ordereddict) for Python 2.6. (`#920 <https://github.com/pytest- + dev/pytest/issues/920>`_) + + +Improved Documentation +---------------------- + +- Clarify ``pytest_configure`` hook call order. (`#2539 + <https://github.com/pytest-dev/pytest/issues/2539>`_) + +- Extend documentation for testing plugin code with the ``pytester`` plugin. + (`#971 <https://github.com/pytest-dev/pytest/issues/971>`_) + + +Trivial/Internal Changes +------------------------ + +- Update help message for ``--strict`` to make it clear it only deals with + unregistered markers, not warnings. (`#2444 <https://github.com/pytest- + dev/pytest/issues/2444>`_) + +- Internal code move: move code for pytest.approx/pytest.raises to own files in + order to cut down the size of python.py (`#2489 <https://github.com/pytest- + dev/pytest/issues/2489>`_) + +- Renamed the utility function ``_pytest.compat._escape_strings`` to + ``_ascii_escaped`` to better communicate the function's purpose. (`#2533 + <https://github.com/pytest-dev/pytest/issues/2533>`_) + +- Improve error message for CollectError with skip/skipif. (`#2546 + <https://github.com/pytest-dev/pytest/issues/2546>`_) + +- Emit warning about ``yield`` tests being deprecated only once per generator. + (`#2562 <https://github.com/pytest-dev/pytest/issues/2562>`_) + +- Ensure final collected line doesn't include artifacts of previous write. + (`#2571 <https://github.com/pytest-dev/pytest/issues/2571>`_) + +- Fixed all flake8 errors and warnings. (`#2581 <https://github.com/pytest- + dev/pytest/issues/2581>`_) + +- Added ``fix-lint`` tox environment to run automatic pep8 fixes on the code. + (`#2582 <https://github.com/pytest-dev/pytest/issues/2582>`_) + +- Turn warnings into errors in pytest's own test suite in order to catch + regressions due to deprecations more promptly. (`#2588 + <https://github.com/pytest-dev/pytest/issues/2588>`_) + +- Show multiple issue links in CHANGELOG entries. (`#2620 + <https://github.com/pytest-dev/pytest/issues/2620>`_) + + +pytest 3.1.3 (2017-07-03) +========================= + +Bug Fixes +--------- + +- Fix decode error in Python 2 for doctests in docstrings. (`#2434 + <https://github.com/pytest-dev/pytest/issues/2434>`_) + +- Exceptions raised during teardown by finalizers are now suppressed until all + finalizers are called, with the initial exception reraised. (`#2440 + <https://github.com/pytest-dev/pytest/issues/2440>`_) + +- Fix incorrect "collected items" report when specifying tests on the command- + line. (`#2464 <https://github.com/pytest-dev/pytest/issues/2464>`_) + +- ``deprecated_call`` in context-manager form now captures deprecation warnings + even if the same warning has already been raised. Also, ``deprecated_call`` + will always produce the same error message (previously it would produce + different messages in context-manager vs. function-call mode). (`#2469 + <https://github.com/pytest-dev/pytest/issues/2469>`_) + +- Fix issue where paths collected by pytest could have triple leading ``/`` + characters. (`#2475 <https://github.com/pytest-dev/pytest/issues/2475>`_) + +- Fix internal error when trying to detect the start of a recursive traceback. + (`#2486 <https://github.com/pytest-dev/pytest/issues/2486>`_) + + +Improved Documentation +---------------------- + +- Explicitly state for which hooks the calls stop after the first non-None + result. (`#2493 <https://github.com/pytest-dev/pytest/issues/2493>`_) + + +Trivial/Internal Changes +------------------------ + +- Create invoke tasks for updating the vendored packages. (`#2474 + <https://github.com/pytest-dev/pytest/issues/2474>`_) + +- Update copyright dates in LICENSE, README.rst and in the documentation. + (`#2499 <https://github.com/pytest-dev/pytest/issues/2499>`_) + + +pytest 3.1.2 (2017-06-08) +========================= + +Bug Fixes +--------- + +- Required options added via ``pytest_addoption`` will no longer prevent using + --help without passing them. (#1999) + +- Respect ``python_files`` in assertion rewriting. (#2121) + +- Fix recursion error detection when frames in the traceback contain objects + that can't be compared (like ``numpy`` arrays). (#2459) + +- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only + when the message contains non-ascii unicode (Python 2 only). (#2463) + +- Added a workaround for Python 3.6 ``WindowsConsoleIO`` breaking due to Pytests's + ``FDCapture``. Other code using console handles might still be affected by the + very same issue and might require further workarounds/fixes, i.e. ``colorama``. + (#2467) + + +Improved Documentation +---------------------- + +- Fix internal API links to ``pluggy`` objects. (#2331) + +- Make it clear that ``pytest.xfail`` stops test execution at the calling point + and improve overall flow of the ``skipping`` docs. (#810) + + +pytest 3.1.1 (2017-05-30) +========================= + +Bug Fixes +--------- + +- pytest warning capture no longer overrides existing warning filters. The + previous behaviour would override all filters and caused regressions in test + suites which configure warning filters to match their needs. Note that as a + side-effect of this is that ``DeprecationWarning`` and + ``PendingDeprecationWarning`` are no longer shown by default. (#2430) + +- Fix issue with non-ascii contents in doctest text files. (#2434) + +- Fix encoding errors for unicode warnings in Python 2. (#2436) + +- ``pytest.deprecated_call`` now captures ``PendingDeprecationWarning`` in + context manager form. (#2441) + + +Improved Documentation +---------------------- + +- Addition of towncrier for changelog management. (#2390) + + +3.1.0 (2017-05-22) +================== + + +New Features +------------ + +* The ``pytest-warnings`` plugin has been integrated into the core and now ``pytest`` automatically + captures and displays warnings at the end of the test session. + + .. warning:: + + This feature may disrupt test suites which apply and treat warnings themselves, and can be + disabled in your ``pytest.ini``: + + .. code-block:: ini + + [pytest] + addopts = -p no:warnings + + See the `warnings documentation page <https://docs.pytest.org/en/stable/warnings.html>`_ for more + information. + + Thanks `@nicoddemus`_ for the PR. + +* Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (`#533`_). + +* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. + Thanks `@wheerd`_ for the PR (`#2101`_). + +* ``pytest.warns`` now checks for subclass relationship rather than + class equality. Thanks `@lesteve`_ for the PR (`#2166`_) + +* ``pytest.raises`` now asserts that the error message matches a text or regex + with the ``match`` keyword argument. Thanks `@Kriechi`_ for the PR. + +* ``pytest.param`` can be used to declare test parameter sets with marks and test ids. + Thanks `@RonnyPfannschmidt`_ for the PR. + + +Changes +------- + +* remove all internal uses of pytest_namespace hooks, + this is to prepare the removal of preloadconfig in pytest 4.0 + Thanks to `@RonnyPfannschmidt`_ for the PR. + +* pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR. + +* It is now possible to skip test classes from being collected by setting a + ``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks + to `@syre`_ for the report and `@lwm`_ for the PR. + +* Change junitxml.py to produce reports that comply with Junitxml schema. + If the same test fails with failure in call and then errors in teardown + we split testcase element into two, one containing the error and the other + the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR. + +* Testcase reports with a ``url`` attribute will now properly write this to junitxml. + Thanks `@fushi`_ for the PR (`#1874`_). + +* Remove common items from dict comparison output when verbosity=1. Also update + the truncation message to make it clearer that pytest truncates all + assertion messages if verbosity < 2 (`#1512`_). + Thanks `@mattduck`_ for the PR + +* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use + ``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for + the PR (`#1952`_). + +* fix `#2013`_: turn RecordedWarning into ``namedtuple``, + to give it a comprehensible repr while preventing unwarranted modification. + +* fix `#2208`_: ensure an iteration limit for _pytest.compat.get_real_func. + Thanks `@RonnyPfannschmidt`_ for the report and PR. + +* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This + makes it easy to write hooks for plugins which will be loaded during collection, for example using the + ``pytest_plugins`` special variable (`#1821`_). + Thanks `@nicoddemus`_ for the PR. + +* Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an + additional parameter. + Thanks `@unsignedint`_ for the PR. + +* Add ``venv`` to the default ``norecursedirs`` setting. + Thanks `@The-Compiler`_ for the PR. + +* ``PluginManager.import_plugin`` now accepts unicode plugin names in Python 2. + Thanks `@reutsharabani`_ for the PR. + +* fix `#2308`_: When using both ``--lf`` and ``--ff``, only the last failed tests are run. + Thanks `@ojii`_ for the PR. + +* Replace minor/patch level version numbers in the documentation with placeholders. + This significantly reduces change-noise as different contributors regnerate + the documentation on different platforms. + Thanks `@RonnyPfannschmidt`_ for the PR. + +* fix `#2391`_: consider pytest_plugins on all plugin modules + Thanks `@RonnyPfannschmidt`_ for the PR. + + +Bug Fixes +--------- + +* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer`` + while using ``capsys`` fixture in python 3. (`#1407`_). + Thanks to `@asottile`_. + +* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather + than ValueErrors in the ``fileno`` method (`#2276`_). + Thanks `@metasyn`_ and `@vlad-dragos`_ for the PR. + +* Fix exception formatting while importing modules when the exception message + contains non-ascii characters (`#2336`_). + Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR. + +* Added documentation related to issue (`#1937`_) + Thanks `@skylarjhdownes`_ for the PR. + +* Allow collecting files with any file extension as Python modules (`#2369`_). + Thanks `@Kodiologist`_ for the PR. + +* Show the correct error message when collect "parametrize" func with wrong args (`#2383`_). + Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR. + + +.. _@davidszotten: https://github.com/davidszotten +.. _@fabioz: https://github.com/fabioz +.. _@fogo: https://github.com/fogo +.. _@fushi: https://github.com/fushi +.. _@Kodiologist: https://github.com/Kodiologist +.. _@Kriechi: https://github.com/Kriechi +.. _@mandeep: https://github.com/mandeep +.. _@mattduck: https://github.com/mattduck +.. _@metasyn: https://github.com/metasyn +.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE +.. _@ojii: https://github.com/ojii +.. _@reutsharabani: https://github.com/reutsharabani +.. _@robin0371: https://github.com/robin0371 +.. _@skylarjhdownes: https://github.com/skylarjhdownes +.. _@unsignedint: https://github.com/unsignedint +.. _@wheerd: https://github.com/wheerd + + +.. _#1407: https://github.com/pytest-dev/pytest/issues/1407 +.. _#1512: https://github.com/pytest-dev/pytest/issues/1512 +.. _#1821: https://github.com/pytest-dev/pytest/issues/1821 +.. _#1874: https://github.com/pytest-dev/pytest/pull/1874 +.. _#1937: https://github.com/pytest-dev/pytest/issues/1937 +.. _#1952: https://github.com/pytest-dev/pytest/pull/1952 +.. _#2007: https://github.com/pytest-dev/pytest/issues/2007 +.. _#2013: https://github.com/pytest-dev/pytest/issues/2013 +.. _#2101: https://github.com/pytest-dev/pytest/pull/2101 +.. _#2166: https://github.com/pytest-dev/pytest/pull/2166 +.. _#2208: https://github.com/pytest-dev/pytest/issues/2208 +.. _#2228: https://github.com/pytest-dev/pytest/issues/2228 +.. _#2276: https://github.com/pytest-dev/pytest/issues/2276 +.. _#2308: https://github.com/pytest-dev/pytest/issues/2308 +.. _#2336: https://github.com/pytest-dev/pytest/issues/2336 +.. _#2369: https://github.com/pytest-dev/pytest/issues/2369 +.. _#2383: https://github.com/pytest-dev/pytest/issues/2383 +.. _#2391: https://github.com/pytest-dev/pytest/issues/2391 +.. _#533: https://github.com/pytest-dev/pytest/issues/533 + + + +3.0.7 (2017-03-14) +================== + + +* Fix issue in assertion rewriting breaking due to modules silently discarding + other modules when importing fails + Notably, importing the ``anydbm`` module is fixed. (`#2248`_). + Thanks `@pfhayes`_ for the PR. + +* junitxml: Fix problematic case where system-out tag occurred twice per testcase + element in the XML report. Thanks `@kkoukiou`_ for the PR. + +* Fix regression, pytest now skips unittest correctly if run with ``--pdb`` + (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR. + +* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_). + Thanks to `@bluetech`_. + +* ``--override-ini`` now correctly overrides some fundamental options like ``python_files`` (`#2238`_). + Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR. + +* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). + Thanks to `@nicoddemus`_ for the PR. + +* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test. + Thanks `@omerhadari`_ for the PR. + +* Skipping plugin now also works with test items generated by custom collectors (`#2231`_). + Thanks to `@vidartf`_. + +* Fix trailing whitespace in console output if no .ini file presented (`#2281`_). Thanks `@fbjorn`_ for the PR. + +* Conditionless ``xfail`` markers no longer rely on the underlying test item + being an instance of ``PyobjMixin``, and can therefore apply to tests not + collected by the built-in python test collector. Thanks `@barneygale`_ for the + PR. + + +.. _@pfhayes: https://github.com/pfhayes +.. _@bluetech: https://github.com/bluetech +.. _@gst: https://github.com/gst +.. _@sirex: https://github.com/sirex +.. _@vidartf: https://github.com/vidartf +.. _@kkoukiou: https://github.com/KKoukiou +.. _@omerhadari: https://github.com/omerhadari +.. _@fbjorn: https://github.com/fbjorn + +.. _#2248: https://github.com/pytest-dev/pytest/issues/2248 +.. _#2137: https://github.com/pytest-dev/pytest/issues/2137 +.. _#2160: https://github.com/pytest-dev/pytest/issues/2160 +.. _#2231: https://github.com/pytest-dev/pytest/issues/2231 +.. _#2234: https://github.com/pytest-dev/pytest/issues/2234 +.. _#2238: https://github.com/pytest-dev/pytest/issues/2238 +.. _#2281: https://github.com/pytest-dev/pytest/issues/2281 + +.. _PEP-479: https://www.python.org/dev/peps/pep-0479/ + + +3.0.6 (2017-01-22) +================== + +* pytest no longer generates ``PendingDeprecationWarning`` from its own operations, which was introduced by mistake in version ``3.0.5`` (`#2118`_). + Thanks to `@nicoddemus`_ for the report and `@RonnyPfannschmidt`_ for the PR. + + +* pytest no longer recognizes coroutine functions as yield tests (`#2129`_). + Thanks to `@malinoff`_ for the PR. + +* Plugins loaded by the ``PYTEST_PLUGINS`` environment variable are now automatically + considered for assertion rewriting (`#2185`_). + Thanks `@nicoddemus`_ for the PR. + +* Improve error message when pytest.warns fails (`#2150`_). The type(s) of the + expected warnings and the list of caught warnings is added to the + error message. Thanks `@lesteve`_ for the PR. + +* Fix ``pytester`` internal plugin to work correctly with latest versions of + ``zope.interface`` (`#1989`_). Thanks `@nicoddemus`_ for the PR. + +* Assert statements of the ``pytester`` plugin again benefit from assertion rewriting (`#1920`_). + Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR. + +* Specifying tests with colons like ``test_foo.py::test_bar`` for tests in + subdirectories with ini configuration files now uses the correct ini file + (`#2148`_). Thanks `@pelme`_. + +* Fail ``testdir.runpytest().assert_outcomes()`` explicitly if the pytest + terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR. + + +.. _@barneygale: https://github.com/barneygale +.. _@lesteve: https://github.com/lesteve +.. _@malinoff: https://github.com/malinoff +.. _@pelme: https://github.com/pelme +.. _@eli-b: https://github.com/eli-b + +.. _#2118: https://github.com/pytest-dev/pytest/issues/2118 + +.. _#1989: https://github.com/pytest-dev/pytest/issues/1989 +.. _#1920: https://github.com/pytest-dev/pytest/issues/1920 +.. _#2129: https://github.com/pytest-dev/pytest/issues/2129 +.. _#2148: https://github.com/pytest-dev/pytest/issues/2148 +.. _#2150: https://github.com/pytest-dev/pytest/issues/2150 +.. _#2185: https://github.com/pytest-dev/pytest/issues/2185 + + +3.0.5 (2016-12-05) +================== + +* Add warning when not passing ``option=value`` correctly to ``-o/--override-ini`` (`#2105`_). + Also improved the help documentation. Thanks to `@mbukatov`_ for the report and + `@lwm`_ for the PR. + +* Now ``--confcutdir`` and ``--junit-xml`` are properly validated if they are directories + and filenames, respectively (`#2089`_ and `#2078`_). Thanks to `@lwm`_ for the PR. + +* Add hint to error message hinting possible missing ``__init__.py`` (`#478`_). Thanks `@DuncanBetts`_. + +* More accurately describe when fixture finalization occurs in documentation (`#687`_). Thanks `@DuncanBetts`_. + +* Provide ``:ref:`` targets for ``recwarn.rst`` so we can use intersphinx referencing. + Thanks to `@dupuy`_ for the report and `@lwm`_ for the PR. + +* In Python 2, use a simple ``+-`` ASCII string in the string representation of ``pytest.approx`` (for example ``"4 +- 4.0e-06"``) + because it is brittle to handle that in different contexts and representations internally in pytest + which can result in bugs such as `#2111`_. In Python 3, the representation still uses ``±`` (for example ``4 ± 4.0e-06``). + Thanks `@kerrick-lyft`_ for the report and `@nicoddemus`_ for the PR. + +* Using ``item.Function``, ``item.Module``, etc., is now issuing deprecation warnings, prefer + ``pytest.Function``, ``pytest.Module``, etc., instead (`#2034`_). + Thanks `@nmundar`_ for the PR. + +* Fix error message using ``approx`` with complex numbers (`#2082`_). + Thanks `@adler-j`_ for the report and `@nicoddemus`_ for the PR. + +* Fixed false-positives warnings from assertion rewrite hook for modules imported more than + once by the ``pytest_plugins`` mechanism. + Thanks `@nicoddemus`_ for the PR. + +* Remove an internal cache which could cause hooks from ``conftest.py`` files in + sub-directories to be called in other directories incorrectly (`#2016`_). + Thanks `@d-b-w`_ for the report and `@nicoddemus`_ for the PR. + +* Remove internal code meant to support earlier Python 3 versions that produced the side effect + of leaving ``None`` in ``sys.modules`` when expressions were evaluated by pytest (for example passing a condition + as a string to ``pytest.mark.skipif``)(`#2103`_). + Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR. + +* Cope gracefully with a .pyc file with no matching .py file (`#2038`_). Thanks + `@nedbat`_. + +.. _@syre: https://github.com/syre +.. _@adler-j: https://github.com/adler-j +.. _@d-b-w: https://github.com/d-b-w +.. _@DuncanBetts: https://github.com/DuncanBetts +.. _@dupuy: https://bitbucket.org/dupuy/ +.. _@kerrick-lyft: https://github.com/kerrick-lyft +.. _@lwm: https://github.com/lwm +.. _@mbukatov: https://github.com/mbukatov +.. _@nedbat: https://github.com/nedbat +.. _@nmundar: https://github.com/nmundar + +.. _#2016: https://github.com/pytest-dev/pytest/issues/2016 +.. _#2034: https://github.com/pytest-dev/pytest/issues/2034 +.. _#2038: https://github.com/pytest-dev/pytest/issues/2038 +.. _#2078: https://github.com/pytest-dev/pytest/issues/2078 +.. _#2082: https://github.com/pytest-dev/pytest/issues/2082 +.. _#2089: https://github.com/pytest-dev/pytest/issues/2089 +.. _#2103: https://github.com/pytest-dev/pytest/issues/2103 +.. _#2105: https://github.com/pytest-dev/pytest/issues/2105 +.. _#2111: https://github.com/pytest-dev/pytest/issues/2111 +.. _#478: https://github.com/pytest-dev/pytest/issues/478 +.. _#687: https://github.com/pytest-dev/pytest/issues/687 + + +3.0.4 (2016-11-09) +================== + +* Import errors when collecting test modules now display the full traceback (`#1976`_). + Thanks `@cwitty`_ for the report and `@nicoddemus`_ for the PR. + +* Fix confusing command-line help message for custom options with two or more ``metavar`` properties (`#2004`_). + Thanks `@okulynyak`_ and `@davehunt`_ for the report and `@nicoddemus`_ for the PR. + +* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_). + Thanks `@nicoddemus`_ for the PR. + +* Fixed cyclic reference when ``pytest.raises`` is used in context-manager form (`#1965`_). Also as a + result of this fix, ``sys.exc_info()`` is left empty in both context-manager and function call usages. + Previously, ``sys.exc_info`` would contain the exception caught by the context manager, + even when the expected exception occurred. + Thanks `@MSeifert04`_ for the report and the PR. + +* Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but + were later marked explicitly by ``pytest.register_assert_rewrite`` + or implicitly as a plugin (`#2005`_). + Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR. + +* Report teardown output on test failure (`#442`_). + Thanks `@matclab`_ for the PR. + +* Fix teardown error message in generated xUnit XML. + Thanks `@gdyuldin`_ for the PR. + +* Properly handle exceptions in ``multiprocessing`` tasks (`#1984`_). + Thanks `@adborden`_ for the report and `@nicoddemus`_ for the PR. + +* Clean up unittest TestCase objects after tests are complete (`#1649`_). + Thanks `@d_b_w`_ for the report and PR. + + +.. _@adborden: https://github.com/adborden +.. _@cwitty: https://github.com/cwitty +.. _@d_b_w: https://github.com/d-b-w +.. _@gdyuldin: https://github.com/gdyuldin +.. _@matclab: https://github.com/matclab +.. _@MSeifert04: https://github.com/MSeifert04 +.. _@okulynyak: https://github.com/okulynyak + +.. _#442: https://github.com/pytest-dev/pytest/issues/442 +.. _#1965: https://github.com/pytest-dev/pytest/issues/1965 +.. _#1976: https://github.com/pytest-dev/pytest/issues/1976 +.. _#1984: https://github.com/pytest-dev/pytest/issues/1984 +.. _#1998: https://github.com/pytest-dev/pytest/issues/1998 +.. _#2004: https://github.com/pytest-dev/pytest/issues/2004 +.. _#2005: https://github.com/pytest-dev/pytest/issues/2005 +.. _#1649: https://github.com/pytest-dev/pytest/issues/1649 + + +3.0.3 (2016-09-28) +================== + +* The ``ids`` argument to ``parametrize`` again accepts ``unicode`` strings + in Python 2 (`#1905`_). + Thanks `@philpep`_ for the report and `@nicoddemus`_ for the PR. + +* Assertions are now being rewritten for plugins in development mode + (``pip install -e``) (`#1934`_). + Thanks `@nicoddemus`_ for the PR. + +* Fix pkg_resources import error in Jython projects (`#1853`_). + Thanks `@raquel-ucl`_ for the PR. + +* Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception + in Python 3 (`#1944`_). + Thanks `@axil`_ for the PR. + +* Explain a bad scope value passed to ``@fixture`` declarations or + a ``MetaFunc.parametrize()`` call. + +* This version includes ``pluggy-0.4.0``, which correctly handles + ``VersionConflict`` errors in plugins (`#704`_). + Thanks `@nicoddemus`_ for the PR. + + +.. _@philpep: https://github.com/philpep +.. _@raquel-ucl: https://github.com/raquel-ucl +.. _@axil: https://github.com/axil +.. _@vlad-dragos: https://github.com/vlad-dragos + +.. _#1853: https://github.com/pytest-dev/pytest/issues/1853 +.. _#1905: https://github.com/pytest-dev/pytest/issues/1905 +.. _#1934: https://github.com/pytest-dev/pytest/issues/1934 +.. _#1944: https://github.com/pytest-dev/pytest/issues/1944 +.. _#704: https://github.com/pytest-dev/pytest/issues/704 + + + + +3.0.2 (2016-09-01) +================== + +* Improve error message when passing non-string ids to ``pytest.mark.parametrize`` (`#1857`_). + Thanks `@okken`_ for the report and `@nicoddemus`_ for the PR. + +* Add ``buffer`` attribute to stdin stub class ``pytest.capture.DontReadFromInput`` + Thanks `@joguSD`_ for the PR. + +* Fix ``UnicodeEncodeError`` when string comparison with unicode has failed. (`#1864`_) + Thanks `@AiOO`_ for the PR. + +* ``pytest_plugins`` is now handled correctly if defined as a string (as opposed as + a sequence of strings) when modules are considered for assertion rewriting. + Due to this bug, much more modules were being rewritten than necessary + if a test suite uses ``pytest_plugins`` to load internal plugins (`#1888`_). + Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR (`#1891`_). + +* Do not call tearDown and cleanups when running tests from + ``unittest.TestCase`` subclasses with ``--pdb`` + enabled. This allows proper post mortem debugging for all applications + which have significant logic in their tearDown machinery (`#1890`_). Thanks + `@mbyt`_ for the PR. + +* Fix use of deprecated ``getfuncargvalue`` method in the internal doctest plugin. + Thanks `@ViviCoder`_ for the report (`#1898`_). + +.. _@joguSD: https://github.com/joguSD +.. _@AiOO: https://github.com/AiOO +.. _@mbyt: https://github.com/mbyt +.. _@ViviCoder: https://github.com/ViviCoder + +.. _#1857: https://github.com/pytest-dev/pytest/issues/1857 +.. _#1864: https://github.com/pytest-dev/pytest/issues/1864 +.. _#1888: https://github.com/pytest-dev/pytest/issues/1888 +.. _#1891: https://github.com/pytest-dev/pytest/pull/1891 +.. _#1890: https://github.com/pytest-dev/pytest/issues/1890 +.. _#1898: https://github.com/pytest-dev/pytest/issues/1898 + + +3.0.1 (2016-08-23) +================== + +* Fix regression when ``importorskip`` is used at module level (`#1822`_). + Thanks `@jaraco`_ and `@The-Compiler`_ for the report and `@nicoddemus`_ for the PR. + +* Fix parametrization scope when session fixtures are used in conjunction + with normal parameters in the same call (`#1832`_). + Thanks `@The-Compiler`_ for the report, `@Kingdread`_ and `@nicoddemus`_ for the PR. + +* Fix internal error when parametrizing tests or fixtures using an empty ``ids`` argument (`#1849`_). + Thanks `@OPpuolitaival`_ for the report and `@nicoddemus`_ for the PR. + +* Fix loader error when running ``pytest`` embedded in a zipfile. + Thanks `@mbachry`_ for the PR. + + +.. _@Kingdread: https://github.com/Kingdread +.. _@mbachry: https://github.com/mbachry +.. _@OPpuolitaival: https://github.com/OPpuolitaival + +.. _#1822: https://github.com/pytest-dev/pytest/issues/1822 +.. _#1832: https://github.com/pytest-dev/pytest/issues/1832 +.. _#1849: https://github.com/pytest-dev/pytest/issues/1849 + + +3.0.0 (2016-08-18) +================== + +**Incompatible changes** + + +A number of incompatible changes were made in this release, with the intent of removing features deprecated for a long +time or change existing behaviors in order to make them less surprising/more useful. + +* Reinterpretation mode has now been removed. Only plain and rewrite + mode are available, consequently the ``--assert=reinterp`` option is + no longer available. This also means files imported from plugins or + ``conftest.py`` will not benefit from improved assertions by + default, you should use ``pytest.register_assert_rewrite()`` to + explicitly turn on assertion rewriting for those files. Thanks + `@flub`_ for the PR. + +* The following deprecated commandline options were removed: + + * ``--genscript``: no longer supported; + * ``--no-assert``: use ``--assert=plain`` instead; + * ``--nomagic``: use ``--assert=plain`` instead; + * ``--report``: use ``-r`` instead; + + Thanks to `@RedBeardCode`_ for the PR (`#1664`_). + +* ImportErrors in plugins now are a fatal error instead of issuing a + pytest warning (`#1479`_). Thanks to `@The-Compiler`_ for the PR. + +* Removed support code for Python 3 versions < 3.3 (`#1627`_). + +* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points + were never documented and a leftover from a pre-virtualenv era. These entry + points also created broken entry points in wheels, so removing them also + removes a source of confusion for users (`#1632`_). + Thanks `@obestwalter`_ for the PR. + +* ``pytest.skip()`` now raises an error when used to decorate a test function, + as opposed to its original intent (to imperatively skip a test inside a test function). Previously + this usage would cause the entire module to be skipped (`#607`_). + Thanks `@omarkohl`_ for the complete PR (`#1519`_). + +* Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C + anyway as soon as they see collection errors, so pytest might as well make that the default behavior (`#1421`_). + A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour. + Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_). + +* Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module. + +* Raise a helpful failure message when requesting a parametrized fixture at runtime, + e.g. with ``request.getfixturevalue``. Previously these parameters were simply + never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])`` + only ran once (`#460`_). + Thanks to `@nikratio`_ for the bug report, `@RedBeardCode`_ and `@tomviner`_ for the PR. + +* ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch`` + so it doesn't conflict with the ``monkeypatch`` fixture. + +* ``--exitfirst / -x`` can now be overridden by a following ``--maxfail=N`` + and is just a synonym for ``--maxfail=1``. + + +**New Features** + +* Support nose-style ``__test__`` attribute on methods of classes, + including unittest-style Classes. If set to ``False``, the test will not be + collected. + +* New ``doctest_namespace`` fixture for injecting names into the + namespace in which doctests run. + Thanks `@milliams`_ for the complete PR (`#1428`_). + +* New ``--doctest-report`` option available to change the output format of diffs + when running (failing) doctests (implements `#1749`_). + Thanks `@hartym`_ for the PR. + +* New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name + for a fixture (to solve the funcarg-shadowing-fixture problem). + Thanks `@novas0x2a`_ for the complete PR (`#1444`_). + +* New ``approx()`` function for easily comparing floating-point numbers in + tests. + Thanks `@kalekundert`_ for the complete PR (`#1441`_). + +* Ability to add global properties in the final xunit output file by accessing + the internal ``junitxml`` plugin (experimental). + Thanks `@tareqalayan`_ for the complete PR `#1454`_). + +* New ``ExceptionInfo.match()`` method to match a regular expression on the + string representation of an exception (`#372`_). + Thanks `@omarkohl`_ for the complete PR (`#1502`_). + +* ``__tracebackhide__`` can now also be set to a callable which then can decide + whether to filter the traceback based on the ``ExceptionInfo`` object passed + to it. Thanks `@The-Compiler`_ for the complete PR (`#1526`_). + +* New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide + friendly strings for custom types. + Thanks `@palaviv`_ for the PR. + +* ``capsys`` and ``capfd`` now have a ``disabled()`` context-manager method, which + can be used to temporarily disable capture within a test. + Thanks `@nicoddemus`_ for the PR. + +* New cli flag ``--fixtures-per-test``: shows which fixtures are being used + for each selected test item. Features doc strings of fixtures by default. + Can also show where fixtures are defined if combined with ``-v``. + Thanks `@hackebrot`_ for the PR. + +* Introduce ``pytest`` command as recommended entry point. Note that ``py.test`` + still works and is not scheduled for removal. Closes proposal + `#1629`_. Thanks `@obestwalter`_ and `@davehunt`_ for the complete PR + (`#1633`_). + +* New cli flags: + + + ``--setup-plan``: performs normal collection and reports + the potential setup and teardown and does not execute any fixtures and tests; + + ``--setup-only``: performs normal collection, executes setup and teardown of + fixtures and reports them; + + ``--setup-show``: performs normal test execution and additionally shows + setup and teardown of fixtures; + + ``--keep-duplicates``: py.test now ignores duplicated paths given in the command + line. To retain the previous behavior where the same test could be run multiple + times by specifying it in the command-line multiple times, pass the ``--keep-duplicates`` + argument (`#1609`_); + + Thanks `@d6e`_, `@kvas-it`_, `@sallner`_, `@ioggstream`_ and `@omarkohl`_ for the PRs. + +* New CLI flag ``--override-ini``/``-o``: overrides values from the ini file. + For example: ``"-o xfail_strict=True"``'. + Thanks `@blueyed`_ and `@fengxx`_ for the PR. + +* New hooks: + + + ``pytest_fixture_setup(fixturedef, request)``: executes fixture setup; + + ``pytest_fixture_post_finalizer(fixturedef)``: called after the fixture's + finalizer and has access to the fixture's result cache. + + Thanks `@d6e`_, `@sallner`_. + +* Issue warnings for asserts whose test is a tuple literal. Such asserts will + never fail because tuples are always truthy and are usually a mistake + (see `#1562`_). Thanks `@kvas-it`_, for the PR. + +* Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``). + Thanks to `@anntzer`_ for the PR. + + +**Changes** + +* Plugins now benefit from assertion rewriting. Thanks + `@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR. + +* Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict + mode and ``"failed"`` in strict mode. Thanks to `@hackebrot`_ for the PR + (`#1795`_) and `@gprasad84`_ for report (`#1546`_). + +* Tests marked with ``xfail(strict=False)`` (the default) now appear in + JUnitXML reports as passing tests instead of skipped. + Thanks to `@hackebrot`_ for the PR (`#1795`_). + +* Highlight path of the file location in the error report to make it easier to copy/paste. + Thanks `@suzaku`_ for the PR (`#1778`_). + +* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like + those marked with the ``@pytest.yield_fixture`` decorator. This change renders + ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements + the preferred way to write teardown code (`#1461`_). + Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR. + +* Explicitly passed parametrize ids do not get escaped to ascii (`#1351`_). + Thanks `@ceridwen`_ for the PR. + +* Fixtures are now sorted in the error message displayed when an unknown + fixture is declared in a test function. + Thanks `@nicoddemus`_ for the PR. + +* ``pytest_terminal_summary`` hook now receives the ``exitstatus`` + of the test session as argument. Thanks `@blueyed`_ for the PR (`#1809`_). + +* Parametrize ids can accept ``None`` as specific test id, in which case the + automatically generated id for that argument will be used. + Thanks `@palaviv`_ for the complete PR (`#1468`_). + +* The parameter to xunit-style setup/teardown methods (``setup_method``, + ``setup_module``, etc.) is now optional and may be omitted. + Thanks `@okken`_ for bringing this to attention and `@nicoddemus`_ for the PR. + +* Improved automatic id generation selection in case of duplicate ids in + parametrize. + Thanks `@palaviv`_ for the complete PR (`#1474`_). + +* Now pytest warnings summary is shown up by default. Added a new flag + ``--disable-pytest-warnings`` to explicitly disable the warnings summary (`#1668`_). + +* Make ImportError during collection more explicit by reminding + the user to check the name of the test module/package(s) (`#1426`_). + Thanks `@omarkohl`_ for the complete PR (`#1520`_). + +* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks + `@mikofski`_ for the report and `@tomviner`_ for the PR (`#1544`_). + +* ``pytest.raises`` in the context manager form accepts a custom + ``message`` to raise when no exception occurred. + Thanks `@palaviv`_ for the complete PR (`#1616`_). + +* ``conftest.py`` files now benefit from assertion rewriting; previously it + was only available for test modules. Thanks `@flub`_, `@sober7`_ and + `@nicoddemus`_ for the PR (`#1619`_). + +* Text documents without any doctests no longer appear as "skipped". + Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). + +* Ensure that a module within a namespace package can be found when it + is specified on the command line together with the ``--pyargs`` + option. Thanks to `@taschini`_ for the PR (`#1597`_). + +* Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding + sub-expressions that happened to be ``False``, assuming this was redundant information. + Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and + `@tomviner`_ for the PR. + +* ``OptionGroup.addoption()`` now checks if option names were already + added before, to make it easier to track down issues like `#1618`_. + Before, you only got exceptions later from ``argparse`` library, + giving no clue about the actual reason for double-added options. + +* ``yield``-based tests are considered deprecated and will be removed in pytest-4.0. + Thanks `@nicoddemus`_ for the PR. + +* ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]`` + to avoid conflicts with other distutils commands (see `#567`_). ``[pytest]`` sections in + ``pytest.ini`` or ``tox.ini`` files are supported and unchanged. + Thanks `@nicoddemus`_ for the PR. + +* Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be + removed in pytest-4.0 (`#1684`_). + Thanks `@nicoddemus`_ for the PR. + +* Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled + for removal in pytest-4.0. It is recommended to pass a list of arguments instead (`#1723`_). + +* Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is + still present but is now considered deprecated. Thanks to `@RedBeardCode`_ and `@tomviner`_ + for the PR (`#1626`_). + +* ``optparse`` type usage now triggers DeprecationWarnings (`#1740`_). + + +* ``optparse`` backward compatibility supports float/complex types (`#457`_). + +* Refined logic for determining the ``rootdir``, considering only valid + paths which fixes a number of issues: `#1594`_, `#1435`_ and `#1471`_. + Updated the documentation according to current behavior. Thanks to + `@blueyed`_, `@davehunt`_ and `@matthiasha`_ for the PR. + +* Always include full assertion explanation. The previous behaviour was hiding + sub-expressions that happened to be False, assuming this was redundant information. + Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and + `@tomviner`_ for PR. + +* Better message in case of not using parametrized variable (see `#1539`_). + Thanks to `@tramwaj29`_ for the PR. + +* Updated docstrings with a more uniform style. + +* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown. + Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to @jgsonesen and + `@tomviner`_ for the PR. + +* No longer display the incorrect test deselection reason (`#1372`_). + Thanks `@ronnypfannschmidt`_ for the PR. + +* The ``--resultlog`` command line option has been deprecated: it is little used + and there are more modern and better alternatives (see `#830`_). + Thanks `@nicoddemus`_ for the PR. + +* Improve error message with fixture lookup errors: add an 'E' to the first + line and '>' to the rest. Fixes `#717`_. Thanks `@blueyed`_ for reporting and + a PR, `@eolo999`_ for the initial PR and `@tomviner`_ for his guidance during + EuroPython2016 sprint. + + +**Bug Fixes** + +* Parametrize now correctly handles duplicated test ids. + +* Fix internal error issue when the ``method`` argument is missing for + ``teardown_method()`` (`#1605`_). + +* Fix exception visualization in case the current working directory (CWD) gets + deleted during testing (`#1235`_). Thanks `@bukzor`_ for reporting. PR by + `@marscher`_. + +* Improve test output for logical expression with brackets (`#925`_). + Thanks `@DRMacIver`_ for reporting and `@RedBeardCode`_ for the PR. + +* Create correct diff for strings ending with newlines (`#1553`_). + Thanks `@Vogtinator`_ for reporting and `@RedBeardCode`_ and + `@tomviner`_ for the PR. + +* ``ConftestImportFailure`` now shows the traceback making it easier to + identify bugs in ``conftest.py`` files (`#1516`_). Thanks `@txomon`_ for + the PR. + +* Text documents without any doctests no longer appear as "skipped". + Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). + +* Fixed collection of classes with custom ``__new__`` method. + Fixes `#1579`_. Thanks to `@Stranger6667`_ for the PR. + +* Fixed scope overriding inside metafunc.parametrize (`#634`_). + Thanks to `@Stranger6667`_ for the PR. + +* Fixed the total tests tally in junit xml output (`#1798`_). + Thanks to `@cboelsen`_ for the PR. + +* Fixed off-by-one error with lines from ``request.node.warn``. + Thanks to `@blueyed`_ for the PR. + + +.. _#1210: https://github.com/pytest-dev/pytest/issues/1210 +.. _#1235: https://github.com/pytest-dev/pytest/issues/1235 +.. _#1351: https://github.com/pytest-dev/pytest/issues/1351 +.. _#1372: https://github.com/pytest-dev/pytest/issues/1372 +.. _#1421: https://github.com/pytest-dev/pytest/issues/1421 +.. _#1426: https://github.com/pytest-dev/pytest/issues/1426 +.. _#1428: https://github.com/pytest-dev/pytest/pull/1428 +.. _#1435: https://github.com/pytest-dev/pytest/issues/1435 +.. _#1441: https://github.com/pytest-dev/pytest/pull/1441 +.. _#1444: https://github.com/pytest-dev/pytest/pull/1444 +.. _#1454: https://github.com/pytest-dev/pytest/pull/1454 +.. _#1461: https://github.com/pytest-dev/pytest/pull/1461 +.. _#1468: https://github.com/pytest-dev/pytest/pull/1468 +.. _#1471: https://github.com/pytest-dev/pytest/issues/1471 +.. _#1474: https://github.com/pytest-dev/pytest/pull/1474 +.. _#1479: https://github.com/pytest-dev/pytest/issues/1479 +.. _#1502: https://github.com/pytest-dev/pytest/pull/1502 +.. _#1503: https://github.com/pytest-dev/pytest/issues/1503 +.. _#1516: https://github.com/pytest-dev/pytest/pull/1516 +.. _#1519: https://github.com/pytest-dev/pytest/pull/1519 +.. _#1520: https://github.com/pytest-dev/pytest/pull/1520 +.. _#1526: https://github.com/pytest-dev/pytest/pull/1526 +.. _#1539: https://github.com/pytest-dev/pytest/issues/1539 +.. _#1544: https://github.com/pytest-dev/pytest/issues/1544 +.. _#1546: https://github.com/pytest-dev/pytest/issues/1546 +.. _#1553: https://github.com/pytest-dev/pytest/issues/1553 +.. _#1562: https://github.com/pytest-dev/pytest/issues/1562 +.. _#1579: https://github.com/pytest-dev/pytest/issues/1579 +.. _#1580: https://github.com/pytest-dev/pytest/pull/1580 +.. _#1594: https://github.com/pytest-dev/pytest/issues/1594 +.. _#1597: https://github.com/pytest-dev/pytest/pull/1597 +.. _#1605: https://github.com/pytest-dev/pytest/issues/1605 +.. _#1616: https://github.com/pytest-dev/pytest/pull/1616 +.. _#1618: https://github.com/pytest-dev/pytest/issues/1618 +.. _#1619: https://github.com/pytest-dev/pytest/issues/1619 +.. _#1626: https://github.com/pytest-dev/pytest/pull/1626 +.. _#1627: https://github.com/pytest-dev/pytest/pull/1627 +.. _#1628: https://github.com/pytest-dev/pytest/pull/1628 +.. _#1629: https://github.com/pytest-dev/pytest/issues/1629 +.. _#1632: https://github.com/pytest-dev/pytest/issues/1632 +.. _#1633: https://github.com/pytest-dev/pytest/pull/1633 +.. _#1664: https://github.com/pytest-dev/pytest/pull/1664 +.. _#1668: https://github.com/pytest-dev/pytest/issues/1668 +.. _#1684: https://github.com/pytest-dev/pytest/pull/1684 +.. _#1723: https://github.com/pytest-dev/pytest/pull/1723 +.. _#1740: https://github.com/pytest-dev/pytest/issues/1740 +.. _#1749: https://github.com/pytest-dev/pytest/issues/1749 +.. _#1778: https://github.com/pytest-dev/pytest/pull/1778 +.. _#1795: https://github.com/pytest-dev/pytest/pull/1795 +.. _#1798: https://github.com/pytest-dev/pytest/pull/1798 +.. _#1809: https://github.com/pytest-dev/pytest/pull/1809 +.. _#372: https://github.com/pytest-dev/pytest/issues/372 +.. _#457: https://github.com/pytest-dev/pytest/issues/457 +.. _#460: https://github.com/pytest-dev/pytest/pull/460 +.. _#567: https://github.com/pytest-dev/pytest/pull/567 +.. _#607: https://github.com/pytest-dev/pytest/issues/607 +.. _#634: https://github.com/pytest-dev/pytest/issues/634 +.. _#717: https://github.com/pytest-dev/pytest/issues/717 +.. _#830: https://github.com/pytest-dev/pytest/issues/830 +.. _#925: https://github.com/pytest-dev/pytest/issues/925 + + +.. _@anntzer: https://github.com/anntzer +.. _@bagerard: https://github.com/bagerard +.. _@BeyondEvil: https://github.com/BeyondEvil +.. _@blueyed: https://github.com/blueyed +.. _@ceridwen: https://github.com/ceridwen +.. _@cboelsen: https://github.com/cboelsen +.. _@csaftoiu: https://github.com/csaftoiu +.. _@d6e: https://github.com/d6e +.. _@davehunt: https://github.com/davehunt +.. _@DRMacIver: https://github.com/DRMacIver +.. _@eolo999: https://github.com/eolo999 +.. _@fengxx: https://github.com/fengxx +.. _@flub: https://github.com/flub +.. _@gprasad84: https://github.com/gprasad84 +.. _@graingert: https://github.com/graingert +.. _@hartym: https://github.com/hartym +.. _@kalekundert: https://github.com/kalekundert +.. _@kvas-it: https://github.com/kvas-it +.. _@marscher: https://github.com/marscher +.. _@mikofski: https://github.com/mikofski +.. _@milliams: https://github.com/milliams +.. _@nikratio: https://github.com/nikratio +.. _@novas0x2a: https://github.com/novas0x2a +.. _@obestwalter: https://github.com/obestwalter +.. _@okken: https://github.com/okken +.. _@olegpidsadnyi: https://github.com/olegpidsadnyi +.. _@omarkohl: https://github.com/omarkohl +.. _@palaviv: https://github.com/palaviv +.. _@RedBeardCode: https://github.com/RedBeardCode +.. _@sallner: https://github.com/sallner +.. _@sober7: https://github.com/sober7 +.. _@Stranger6667: https://github.com/Stranger6667 +.. _@suzaku: https://github.com/suzaku +.. _@tareqalayan: https://github.com/tareqalayan +.. _@taschini: https://github.com/taschini +.. _@tramwaj29: https://github.com/tramwaj29 +.. _@txomon: https://github.com/txomon +.. _@Vogtinator: https://github.com/Vogtinator +.. _@matthiasha: https://github.com/matthiasha + + +2.9.2 (2016-05-31) +================== + +**Bug Fixes** + +* fix `#510`_: skip tests where one parameterize dimension was empty + thanks Alex Stapleton for the Report and `@RonnyPfannschmidt`_ for the PR + +* Fix Xfail does not work with condition keyword argument. + Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_ + for PR the (`#1524`_). + +* Fix win32 path issue when putting custom config file with absolute path + in ``pytest.main("-c your_absolute_path")``. + +* Fix maximum recursion depth detection when raised error class is not aware + of unicode/encoded bytes. + Thanks `@prusse-martin`_ for the PR (`#1506`_). + +* Fix ``pytest.mark.skip`` mark when used in strict mode. + Thanks `@pquentin`_ for the PR and `@RonnyPfannschmidt`_ for + showing how to fix the bug. + +* Minor improvements and fixes to the documentation. + Thanks `@omarkohl`_ for the PR. + +* Fix ``--fixtures`` to show all fixture definitions as opposed to just + one per fixture name. + Thanks to `@hackebrot`_ for the PR. + +.. _#510: https://github.com/pytest-dev/pytest/issues/510 +.. _#1506: https://github.com/pytest-dev/pytest/pull/1506 +.. _#1496: https://github.com/pytest-dev/pytest/issues/1496 +.. _#1524: https://github.com/pytest-dev/pytest/pull/1524 + +.. _@prusse-martin: https://github.com/prusse-martin +.. _@astraw38: https://github.com/astraw38 + + +2.9.1 (2016-03-17) +================== + +**Bug Fixes** + +* Improve error message when a plugin fails to load. + Thanks `@nicoddemus`_ for the PR. + +* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_): + ``pytest.fail`` with non-ascii characters raises an internal pytest error. + Thanks `@nicoddemus`_ for the PR. + +* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs + contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_). + +* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors + containing non-ascii lines at the point of failure generated an internal + py.test error. + Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR. + +* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize + attempt to decode it as utf-8 ignoring errors. + +* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line. + +* Fix (`#138`_): better reporting for python 3.3+ chained exceptions + +.. _#1437: https://github.com/pytest-dev/pytest/issues/1437 +.. _#469: https://github.com/pytest-dev/pytest/issues/469 +.. _#1431: https://github.com/pytest-dev/pytest/pull/1431 +.. _#649: https://github.com/pytest-dev/pytest/issues/649 +.. _#138: https://github.com/pytest-dev/pytest/issues/138 + +.. _@asottile: https://github.com/asottile + + +2.9.0 (2016-02-29) +================== + +**New Features** + +* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests. + Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_). + +* ``--doctest-glob`` may now be passed multiple times in the command-line. + Thanks `@jab`_ and `@nicoddemus`_ for the PR. + +* New ``-rp`` and ``-rP`` reporting options give the summary and full output + of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR. + +* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS`` + tests to fail the test suite (defaulting to ``False``). There's also a + ``xfail_strict`` ini option that can be used to configure it project-wise. + Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_). + +* ``Parser.addini`` now supports options of type ``bool``. + Thanks `@nicoddemus`_ for the PR. + +* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings + in doctest output (similar to ``ALLOW_UNICODE``). + Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_). + +* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors. + Fixes `#1366`_. + Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR. + +* Catch ``IndexError`` exceptions when getting exception source location. + Fixes a pytest internal error for dynamically generated code (fixtures and tests) + where source lines are fake by intention. + +**Changes** + +* **Important**: `py.code <https://pylib.readthedocs.io/en/stable/code.html>`_ has been + merged into the ``pytest`` repository as ``pytest._code``. This decision + was made because ``py.code`` had very few uses outside ``pytest`` and the + fact that it was in a different repository made it difficult to fix bugs on + its code in a timely manner. The team hopes with this to be able to better + refactor out and improve that code. + This change shouldn't affect users, but it is useful to let users aware + if they encounter any strange behavior. + + Keep in mind that the code for ``pytest._code`` is **private** and + **experimental**, so you definitely should not import it explicitly! + + Please note that the original ``py.code`` is still available in + `pylib <https://pylib.readthedocs.io>`_. + +* ``pytest_enter_pdb`` now optionally receives the pytest config object. + Thanks `@nicoddemus`_ for the PR. + +* Removed code and documentation for Python 2.5 or lower versions, + including removal of the obsolete ``_pytest.assertion.oldinterpret`` module. + Thanks `@nicoddemus`_ for the PR (`#1226`_). + +* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is + found in the environment, even when ``-vv`` isn't used. + Thanks `@The-Compiler`_ for the PR. + +* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and + ``--failed-first`` respectively. + Thanks `@MichaelAquilina`_ for the PR. + +* Added expected exceptions to ``pytest.raises`` fail message. + +* Collection only displays progress ("collecting X items") when in a terminal. + This avoids cluttering the output when using ``--color=yes`` to obtain + colors in CI integrations systems (`#1397`_). + +**Bug Fixes** + +* The ``-s`` and ``-c`` options should now work under ``xdist``; + ``Config.fromdictargs`` now represents its input much more faithfully. + Thanks to `@bukzor`_ for the complete PR (`#680`_). + +* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting. + Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR. + +* Fix formatting utf-8 explanation messages (`#1379`_). + Thanks `@biern`_ for the PR. + +* Fix `traceback style docs`_ to describe all of the available options + (auto/long/short/line/native/no), with ``auto`` being the default since v2.6. + Thanks `@hackebrot`_ for the PR. + +* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records + with same name. + +.. _`traceback style docs`: https://pytest.org/en/stable/usage.html#modifying-python-traceback-printing + +.. _#1609: https://github.com/pytest-dev/pytest/issues/1609 +.. _#1422: https://github.com/pytest-dev/pytest/issues/1422 +.. _#1379: https://github.com/pytest-dev/pytest/issues/1379 +.. _#1366: https://github.com/pytest-dev/pytest/issues/1366 +.. _#1040: https://github.com/pytest-dev/pytest/pull/1040 +.. _#680: https://github.com/pytest-dev/pytest/issues/680 +.. _#1287: https://github.com/pytest-dev/pytest/pull/1287 +.. _#1226: https://github.com/pytest-dev/pytest/pull/1226 +.. _#1290: https://github.com/pytest-dev/pytest/pull/1290 +.. _#1355: https://github.com/pytest-dev/pytest/pull/1355 +.. _#1397: https://github.com/pytest-dev/pytest/issues/1397 +.. _@biern: https://github.com/biern +.. _@MichaelAquilina: https://github.com/MichaelAquilina +.. _@bukzor: https://github.com/bukzor +.. _@hpk42: https://github.com/hpk42 +.. _@nicoddemus: https://github.com/nicoddemus +.. _@jab: https://github.com/jab +.. _@codewarrior0: https://github.com/codewarrior0 +.. _@jaraco: https://github.com/jaraco +.. _@The-Compiler: https://github.com/The-Compiler +.. _@Shinkenjoe: https://github.com/Shinkenjoe +.. _@tomviner: https://github.com/tomviner +.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt +.. _@rabbbit: https://github.com/rabbbit +.. _@hackebrot: https://github.com/hackebrot +.. _@pquentin: https://github.com/pquentin +.. _@ioggstream: https://github.com/ioggstream + +2.8.7 (2016-01-24) +================== + +- fix #1338: use predictable object resolution for monkeypatch + +2.8.6 (2016-01-21) +================== + +- fix #1259: allow for double nodeids in junitxml, + this was a regression failing plugins combinations + like pytest-pep8 + pytest-flakes + +- Workaround for exception that occurs in pyreadline when using + ``--pdb`` with standard I/O capture enabled. + Thanks Erik M. Bray for the PR. + +- fix #900: Better error message in case the target of a ``monkeypatch`` call + raises an ``ImportError``. + +- fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1). + Thanks David R. MacIver for the report and Bruno Oliveira for the PR. + +- fix #1223: captured stdout and stderr are now properly displayed before + entering pdb when ``--pdb`` is used instead of being thrown away. + Thanks Cal Leeming for the PR. + +- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now + properly displayed. + Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR. + +- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode. + Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR. + +- fix #1334: Add captured stdout to jUnit XML report on setup error. + Thanks Georgy Dyuldin for the PR. + + +2.8.5 (2015-12-11) +================== + +- fix #1243: fixed issue where class attributes injected during collection could break pytest. + PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help. + +- fix #1074: precompute junitxml chunks instead of storing the whole tree in objects + Thanks Bruno Oliveira for the report and Ronny Pfannschmidt for the PR + +- fix #1238: fix ``pytest.deprecated_call()`` receiving multiple arguments + (Regression introduced in 2.8.4). Thanks Alex Gaynor for the report and + Bruno Oliveira for the PR. + + +2.8.4 (2015-12-06) +================== + +- fix #1190: ``deprecated_call()`` now works when the deprecated + function has been already called by another test in the same + module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the + PR. + +- fix #1198: ``--pastebin`` option now works on Python 3. Thanks + Mehdy Khoshnoody for the PR. + +- fix #1219: ``--pastebin`` now works correctly when captured output contains + non-ascii characters. Thanks Bruno Oliveira for the PR. + +- fix #1204: another error when collecting with a nasty __getattr__(). + Thanks Florian Bruhin for the PR. + +- fix the summary printed when no tests did run. + Thanks Florian Bruhin for the PR. +- fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist + +- a number of documentation modernizations wrt good practices. + Thanks Bruno Oliveira for the PR. + +2.8.3 (2015-11-18) +================== + +- fix #1169: add __name__ attribute to testcases in TestCaseFunction to + support the @unittest.skip decorator on functions and methods. + Thanks Lee Kamentsky for the PR. + +- fix #1035: collecting tests if test module level obj has __getattr__(). + Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR. + +- fix #331: don't collect tests if their failure cannot be reported correctly + e.g. they are a callable instance of a class. + +- fix #1133: fixed internal error when filtering tracebacks where one entry + belongs to a file which is no longer available. + Thanks Bruno Oliveira for the PR. + +- enhancement made to highlight in red the name of the failing tests so + they stand out in the output. + Thanks Gabriel Reis for the PR. + +- add more talks to the documentation +- extend documentation on the --ignore cli option +- use pytest-runner for setuptools integration +- minor fixes for interaction with OS X El Capitan + system integrity protection (thanks Florian) + + +2.8.2 (2015-10-07) +================== + +- fix #1085: proper handling of encoding errors when passing encoded byte + strings to pytest.parametrize in Python 2. + Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR. + +- fix #1087: handling SystemError when passing empty byte strings to + pytest.parametrize in Python 3. + Thanks Paul Kehrer for the report and Bruno Oliveira for the PR. + +- fix #995: fixed internal error when filtering tracebacks where one entry + was generated by an exec() statement. + Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko + for contributing and Bruno Oliveira for the PR. + +- fix #1100 and #1057: errors when using autouse fixtures and doctest modules. + Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno + Oliveira for the PR. + +2.8.1 (2015-09-29) +================== + +- fix #1034: Add missing nodeid on pytest_logwarning call in + addhook. Thanks Simon Gomizelj for the PR. + +- 'deprecated_call' is now only satisfied with a DeprecationWarning or + PendingDeprecationWarning. Before 2.8.0, it accepted any warning, and 2.8.0 + made it accept only DeprecationWarning (but not PendingDeprecationWarning). + Thanks Alex Gaynor for the issue and Eric Hunsberger for the PR. + +- fix issue #1073: avoid calling __getattr__ on potential plugin objects. + This fixes an incompatibility with pytest-django. Thanks Andreas Pelme, + Bruno Oliveira and Ronny Pfannschmidt for contributing and Holger Krekel + for the fix. + +- Fix issue #704: handle versionconflict during plugin loading more + gracefully. Thanks Bruno Oliveira for the PR. + +- Fix issue #1064: ""--junitxml" regression when used with the + "pytest-xdist" plugin, with test reports being assigned to the wrong tests. + Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR. + +- (experimental) adapt more SEMVER style versioning and change meaning of + master branch in git repo: "master" branch now keeps the bug fixes, changes + aimed for micro releases. "features" branch will only be released + with minor or major pytest releases. + +- Fix issue #766 by removing documentation references to distutils. + Thanks Russel Winder. + +- Fix issue #1030: now byte-strings are escaped to produce item node ids + to make them always serializable. + Thanks Andy Freeland for the report and Bruno Oliveira for the PR. + +- Python 2: if unicode parametrized values are convertible to ascii, their + ascii representation is used for the node id. + +- Fix issue #411: Add __eq__ method to assertion comparison example. + Thanks Ben Webb. +- Fix issue #653: deprecated_call can be used as context manager. + +- fix issue 877: properly handle assertion explanations with non-ascii repr + Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR. + +- fix issue 1029: transform errors when writing cache values into pytest-warnings + +2.8.0 (2015-09-18) +================== + +- new ``--lf`` and ``-ff`` options to run only the last failing tests or + "failing tests first" from the last run. This functionality is provided + through porting the formerly external pytest-cache plugin into pytest core. + BACKWARD INCOMPAT: if you used pytest-cache's functionality to persist + data between test runs be aware that we don't serialize sets anymore. + Thanks Ronny Pfannschmidt for most of the merging work. + +- "-r" option now accepts "a" to include all possible reports, similar + to passing "fEsxXw" explicitly (issue960). + Thanks Abhijeet Kasurde for the PR. + +- avoid python3.5 deprecation warnings by introducing version + specific inspection helpers, thanks Michael Droettboom. + +- fix issue562: @nose.tools.istest now fully respected. + +- fix issue934: when string comparison fails and a diff is too large to display + without passing -vv, still show a few lines of the diff. + Thanks Florian Bruhin for the report and Bruno Oliveira for the PR. + +- fix issue736: Fix a bug where fixture params would be discarded when combined + with parametrization markers. + Thanks to Markus Unterwaditzer for the PR. + +- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the + ``u`` prefix is stripped from unicode strings in expected doctest output. This + allows doctests which use unicode to run in Python 2 and 3 unchanged. + Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR. + +- parametrize now also generates meaningful test IDs for enum, regex and class + objects (as opposed to class instances). + Thanks to Florian Bruhin for the PR. + +- Add 'warns' to assert that warnings are thrown (like 'raises'). + Thanks to Eric Hunsberger for the PR. + +- Fix issue683: Do not apply an already applied mark. Thanks ojake for the PR. + +- Deal with capturing failures better so fewer exceptions get lost to + /dev/null. Thanks David Szotten for the PR. + +- fix issue730: deprecate and warn about the --genscript option. + Thanks Ronny Pfannschmidt for the report and Christian Pommranz for the PR. + +- fix issue751: multiple parametrize with ids bug if it parametrizes class with + two or more test methods. Thanks Sergey Chipiga for reporting and Jan + Bednarik for PR. + +- fix issue82: avoid loading conftest files from setup.cfg/pytest.ini/tox.ini + files and upwards by default (--confcutdir can still be set to override this). + Thanks Bruno Oliveira for the PR. + +- fix issue768: docstrings found in python modules were not setting up session + fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR. + +- added ``tmpdir_factory``, a session-scoped fixture that can be used to create + directories under the base temporary directory. Previously this object was + installed as a ``_tmpdirhandler`` attribute of the ``config`` object, but now it + is part of the official API and using ``config._tmpdirhandler`` is + deprecated. + Thanks Bruno Oliveira for the PR. + +- fix issue808: pytest's internal assertion rewrite hook now implements the + optional PEP302 get_data API so tests can access data files next to them. + Thanks xmo-odoo for request and example and Bruno Oliveira for + the PR. + +- rootdir and inifile are now displayed during usage errors to help + users diagnose problems such as unexpected ini files which add + unknown options being picked up by pytest. Thanks to Pavel Savchenko for + bringing the problem to attention in #821 and Bruno Oliveira for the PR. + +- Summary bar now is colored yellow for warning + situations such as: all tests either were skipped or xpass/xfailed, + or no tests were run at all (this is a partial fix for issue500). + +- fix issue812: pytest now exits with status code 5 in situations where no + tests were run at all, such as the directory given in the command line does + not contain any tests or as result of a command line option filters + all out all tests (-k for example). + Thanks Eric Siegerman (issue812) and Bruno Oliveira for the PR. + +- Summary bar now is colored yellow for warning + situations such as: all tests either were skipped or xpass/xfailed, + or no tests were run at all (related to issue500). + Thanks Eric Siegerman. + +- New ``testpaths`` ini option: list of directories to search for tests + when executing pytest from the root directory. This can be used + to speed up test collection when a project has well specified directories + for tests, being usually more practical than configuring norecursedirs for + all directories that do not contain tests. + Thanks to Adrian for idea (#694) and Bruno Oliveira for the PR. + +- fix issue713: JUnit XML reports for doctest failures. + Thanks Punyashloka Biswal. + +- fix issue970: internal pytest warnings now appear as "pytest-warnings" in + the terminal instead of "warnings", so it is clear for users that those + warnings are from pytest and not from the builtin "warnings" module. + Thanks Bruno Oliveira. + +- Include setup and teardown in junitxml test durations. + Thanks Janne Vanhala. + +- fix issue735: assertion failures on debug versions of Python 3.4+ + +- new option ``--import-mode`` to allow to change test module importing + behaviour to append to sys.path instead of prepending. This better allows + to run test modules against installed versions of a package even if the + package under test has the same import root. In this example:: + + testing/__init__.py + testing/test_pkg_under_test.py + pkg_under_test/ + + the tests will run against the installed version + of pkg_under_test when ``--import-mode=append`` is used whereas + by default they would always pick up the local version. Thanks Holger Krekel. + +- pytester: add method ``TmpTestdir.delete_loaded_modules()``, and call it + from ``inline_run()`` to allow temporary modules to be reloaded. + Thanks Eduardo Schettino. + +- internally refactor pluginmanager API and code so that there + is a clear distinction between a pytest-agnostic rather simple + pluginmanager and the PytestPluginManager which adds a lot of + behaviour, among it handling of the local conftest files. + In terms of documented methods this is a backward compatible + change but it might still break 3rd party plugins which relied on + details like especially the pluginmanager.add_shutdown() API. + Thanks Holger Krekel. + +- pluginmanagement: introduce ``pytest.hookimpl`` and + ``pytest.hookspec`` decorators for setting impl/spec + specific parameters. This substitutes the previous + now deprecated use of ``pytest.mark`` which is meant to + contain markers for test functions only. + +- write/refine docs for "writing plugins" which now have their + own page and are separate from the "using/installing plugins`` page. + +- fix issue732: properly unregister plugins from any hook calling + sites allowing to have temporary plugins during test execution. + +- deprecate and warn about ``__multicall__`` argument in hook + implementations. Use the ``hookwrapper`` mechanism instead already + introduced with pytest-2.7. + +- speed up pytest's own test suite considerably by using inprocess + tests by default (testrun can be modified with --runpytest=subprocess + to create subprocesses in many places instead). The main + APIs to run pytest in a test is "runpytest()" or "runpytest_subprocess" + and "runpytest_inprocess" if you need a particular way of running + the test. In all cases you get back a RunResult but the inprocess + one will also have a "reprec" attribute with the recorded events/reports. + +- fix monkeypatch.setattr("x.y", raising=False) to actually not raise + if "y" is not a pre-existing attribute. Thanks Florian Bruhin. + +- fix issue741: make running output from testdir.run copy/pasteable + Thanks Bruno Oliveira. + +- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files. + +- add ``file`` and ``line`` attributes to JUnit-XML output. + +- fix issue890: changed extension of all documentation files from ``txt`` to + ``rst``. Thanks to Abhijeet for the PR. + +- fix issue714: add ability to apply indirect=True parameter on particular argnames. + Thanks Elizaveta239. + +- fix issue890: changed extension of all documentation files from ``txt`` to + ``rst``. Thanks to Abhijeet for the PR. + +- fix issue957: "# doctest: SKIP" option will now register doctests as SKIPPED + rather than PASSED. + Thanks Thomas Grainger for the report and Bruno Oliveira for the PR. + +- issue951: add new record_xml_property fixture, that supports logging + additional information on xml output. Thanks David Diaz for the PR. + +- issue949: paths after normal options (for example ``-s``, ``-v``, etc) are now + properly used to discover ``rootdir`` and ``ini`` files. + Thanks Peter Lauri for the report and Bruno Oliveira for the PR. + +2.7.3 (2015-09-15) +================== + +- Allow 'dev', 'rc', or other non-integer version strings in ``importorskip``. + Thanks to Eric Hunsberger for the PR. + +- fix issue856: consider --color parameter in all outputs (for example + --fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR. + +- fix issue855: passing str objects as ``plugins`` argument to pytest.main + is now interpreted as a module name to be imported and registered as a + plugin, instead of silently having no effect. + Thanks xmo-odoo for the report and Bruno Oliveira for the PR. + +- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks + Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and + Thomas Kluyver. + +- fix issue842: applying markers in classes no longer propagate this markers + to superclasses which also have markers. + Thanks xmo-odoo for the report and Bruno Oliveira for the PR. + +- preserve warning functions after call to pytest.deprecated_call. Thanks + Pieter Mulder for PR. + +- fix issue854: autouse yield_fixtures defined as class members of + unittest.TestCase subclasses now work as expected. + Thannks xmo-odoo for the report and Bruno Oliveira for the PR. + +- fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the + fixtures declared on the first one. + Thanks Florian Bruhin for reporting and Bruno Oliveira for the PR. + +- fix issue863: skipped tests now report the correct reason when a skip/xfail + condition is met when using multiple markers. + Thanks Raphael Pierzina for reporting and Bruno Oliveira for the PR. + +- optimized tmpdir fixture initialization, which should make test sessions + faster (specially when using pytest-xdist). The only visible effect + is that now pytest uses a subdirectory in the $TEMP directory for all + directories created by this fixture (defaults to $TEMP/pytest-$USER). + Thanks Bruno Oliveira for the PR. + +2.7.2 (2015-06-23) +================== + +- fix issue767: pytest.raises value attribute does not contain the exception + instance on Python 2.6. Thanks Eric Siegerman for providing the test + case and Bruno Oliveira for PR. + +- Automatically create directory for junitxml and results log. + Thanks Aron Curzon. + +- fix issue713: JUnit XML reports for doctest failures. + Thanks Punyashloka Biswal. + +- fix issue735: assertion failures on debug versions of Python 3.4+ + Thanks Benjamin Peterson. + +- fix issue114: skipif marker reports to internal skipping plugin; + Thanks Floris Bruynooghe for reporting and Bruno Oliveira for the PR. + +- fix issue748: unittest.SkipTest reports to internal pytest unittest plugin. + Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR. + +- fix issue718: failed to create representation of sets containing unsortable + elements in python 2. Thanks Edison Gustavo Muenz. + +- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29 + which has a refined algorithm for traceback generation. + + +2.7.1 (2015-05-19) +================== + +- fix issue731: do not get confused by the braces which may be present + and unbalanced in an object's repr while collapsing False + explanations. Thanks Carl Meyer for the report and test case. + +- fix issue553: properly handling inspect.getsourcelines failures in + FixtureLookupError which would lead to an internal error, + obfuscating the original problem. Thanks talljosh for initial + diagnose/patch and Bruno Oliveira for final patch. + +- fix issue660: properly report scope-mismatch-access errors + independently from ordering of fixture arguments. Also + avoid the pytest internal traceback which does not provide + information to the user. Thanks Holger Krekel. + +- streamlined and documented release process. Also all versions + (in setup.py and documentation generation) are now read + from _pytest/__init__.py. Thanks Holger Krekel. + +- fixed docs to remove the notion that yield-fixtures are experimental. + They are here to stay :) Thanks Bruno Oliveira. + +- Support building wheels by using environment markers for the + requirements. Thanks Ionel Maries Cristian. + +- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing + when tests raised SystemExit. Thanks Holger Krekel. + +- reintroduced _pytest fixture of the pytester plugin which is used + at least by pytest-xdist. + +2.7.0 (2015-03-26) +================== + +- fix issue435: make reload() work when assert rewriting is active. + Thanks Daniel Hahler. + +- fix issue616: conftest.py files and their contained fixtures are now + properly considered for visibility, independently from the exact + current working directory and test arguments that are used. + Many thanks to Eric Siegerman and his PR235 which contains + systematic tests for conftest visibility and now passes. + This change also introduces the concept of a ``rootdir`` which + is printed as a new pytest header and documented in the pytest + customize web page. + +- change reporting of "diverted" tests, i.e. tests that are collected + in one file but actually come from another (e.g. when tests in a test class + come from a base class in a different file). We now show the nodeid + and indicate via a postfix the other file. + +- add ability to set command line options by environment variable PYTEST_ADDOPTS. + +- added documentation on the new pytest-dev teams on bitbucket and + github. See https://pytest.org/en/stable/contributing.html . + Thanks to Anatoly for pushing and initial work on this. + +- fix issue650: new option ``--docttest-ignore-import-errors`` which + will turn import errors in doctests into skips. Thanks Charles Cloud + for the complete PR. + +- fix issue655: work around different ways that cause python2/3 + to leak sys.exc_info into fixtures/tests causing failures in 3rd party code + +- fix issue615: assertion rewriting did not correctly escape % signs + when formatting boolean operations, which tripped over mixing + booleans with modulo operators. Thanks to Tom Viner for the report, + triaging and fix. + +- implement issue351: add ability to specify parametrize ids as a callable + to generate custom test ids. Thanks Brianna Laugher for the idea and + implementation. + +- introduce and document new hookwrapper mechanism useful for plugins + which want to wrap the execution of certain hooks for their purposes. + This supersedes the undocumented ``__multicall__`` protocol which + pytest itself and some external plugins use. Note that pytest-2.8 + is scheduled to drop supporting the old ``__multicall__`` + and only support the hookwrapper protocol. + +- majorly speed up invocation of plugin hooks + +- use hookwrapper mechanism in builtin pytest plugins. + +- add a doctest ini option for doctest flags, thanks Holger Peters. + +- add note to docs that if you want to mark a parameter and the + parameter is a callable, you also need to pass in a reason to disambiguate + it from the "decorator" case. Thanks Tom Viner. + +- "python_classes" and "python_functions" options now support glob-patterns + for test discovery, as discussed in issue600. Thanks Ldiary Translations. + +- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff). + +- fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise). + +- On failure, the ``sys.last_value``, ``sys.last_type`` and + ``sys.last_traceback`` are set, so that a user can inspect the error + via postmortem debugging (almarklein). + +2.6.4 (2014-10-24) +================== + +- Improve assertion failure reporting on iterables, by using ndiff and + pprint. + +- removed outdated japanese docs from source tree. + +- docs for "pytest_addhooks" hook. Thanks Bruno Oliveira. + +- updated plugin index docs. Thanks Bruno Oliveira. + +- fix issue557: with "-k" we only allow the old style "-" for negation + at the beginning of strings and even that is deprecated. Use "not" instead. + This should allow to pick parametrized tests where "-" appeared in the parameter. + +- fix issue604: Escape % character in the assertion message. + +- fix issue620: add explanation in the --genscript target about what + the binary blob means. Thanks Dinu Gherman. + +- fix issue614: fixed pastebin support. + + +- fix issue620: add explanation in the --genscript target about what + the binary blob means. Thanks Dinu Gherman. + +- fix issue614: fixed pastebin support. + +2.6.3 (2014-09-24) +================== + +- fix issue575: xunit-xml was reporting collection errors as failures + instead of errors, thanks Oleg Sinyavskiy. + +- fix issue582: fix setuptools example, thanks Laszlo Papp and Ronny + Pfannschmidt. + +- Fix infinite recursion bug when pickling capture.EncodedFile, thanks + Uwe Schmitt. + +- fix issue589: fix bad interaction with numpy and others when showing + exceptions. Check for precise "maximum recursion depth exceed" exception + instead of presuming any RuntimeError is that one (implemented in py + dep). Thanks Charles Cloud for analysing the issue. + +- fix conftest related fixture visibility issue: when running with a + CWD outside of a test package pytest would get fixture discovery wrong. + Thanks to Wolfgang Schnerring for figuring out a reproducible example. + +- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the + timeout when interactively entering pdb). Thanks Wolfgang Schnerring. + +- check xfail/skip also with non-python function test items. Thanks + Floris Bruynooghe. + +2.6.2 (2014-09-05) +================== + +- Added function pytest.freeze_includes(), which makes it easy to embed + pytest into executables using tools like cx_freeze. + See docs for examples and rationale. Thanks Bruno Oliveira. + +- Improve assertion rewriting cache invalidation precision. + +- fixed issue561: adapt autouse fixture example for python3. + +- fixed issue453: assertion rewriting issue with __repr__ containing + "\n{", "\n}" and "\n~". + +- fix issue560: correctly display code if an "else:" or "finally:" is + followed by statements on the same line. + +- Fix example in monkeypatch documentation, thanks t-8ch. + +- fix issue572: correct tmpdir doc example for python3. + +- Do not mark as universal wheel because Python 2.6 is different from + other builds due to the extra argparse dependency. Fixes issue566. + Thanks sontek. + +- Implement issue549: user-provided assertion messages now no longer + replace the py.test introspection message but are shown in addition + to them. + +2.6.1 (2014-08-07) +================== + +- No longer show line numbers in the --verbose output, the output is now + purely the nodeid. The line number is still shown in failure reports. + Thanks Floris Bruynooghe. + +- fix issue437 where assertion rewriting could cause pytest-xdist worker nodes + to collect different tests. Thanks Bruno Oliveira. + +- fix issue555: add "errors" attribute to capture-streams to satisfy + some distutils and possibly other code accessing sys.stdout.errors. + +- fix issue547 capsys/capfd also work when output capturing ("-s") is disabled. + +- address issue170: allow pytest.mark.xfail(...) to specify expected exceptions via + an optional "raises=EXC" argument where EXC can be a single exception + or a tuple of exception classes. Thanks David Mohr for the complete + PR. + +- fix integration of pytest with unittest.mock.patch decorator when + it uses the "new" argument. Thanks Nicolas Delaby for test and PR. + +- fix issue with detecting conftest files if the arguments contain + "::" node id specifications (copy pasted from "-v" output) + +- fix issue544 by only removing "@NUM" at the end of "::" separated parts + and if the part has a ".py" extension + +- don't use py.std import helper, rather import things directly. + Thanks Bruno Oliveira. + +2.6 +=== + +- Cache exceptions from fixtures according to their scope (issue 467). + +- fix issue537: Avoid importing old assertion reinterpretation code by default. + +- fix issue364: shorten and enhance tracebacks representation by default. + The new "--tb=auto" option (default) will only display long tracebacks + for the first and last entry. You can get the old behaviour of printing + all entries as long entries with "--tb=long". Also short entries by + default are now printed very similarly to "--tb=native" ones. + +- fix issue514: teach assertion reinterpretation about private class attributes + +- change -v output to include full node IDs of tests. Users can copy + a node ID from a test run, including line number, and use it as a + positional argument in order to run only a single test. + +- fix issue 475: fail early and comprehensible if calling + pytest.raises with wrong exception type. + +- fix issue516: tell in getting-started about current dependencies. + +- cleanup setup.py a bit and specify supported versions. Thanks Jurko + Gospodnetic for the PR. + +- change XPASS colour to yellow rather then red when tests are run + with -v. + +- fix issue473: work around mock putting an unbound method into a class + dict when double-patching. + +- fix issue498: if a fixture finalizer fails, make sure that + the fixture is still invalidated. + +- fix issue453: the result of the pytest_assertrepr_compare hook now gets + it's newlines escaped so that format_exception does not blow up. + +- internal new warning system: pytest will now produce warnings when + it detects oddities in your test collection or execution. + Warnings are ultimately sent to a new pytest_logwarning hook which is + currently only implemented by the terminal plugin which displays + warnings in the summary line and shows more details when -rw (report on + warnings) is specified. + +- change skips into warnings for test classes with an __init__ and + callables in test modules which look like a test but are not functions. + +- fix issue436: improved finding of initial conftest files from command + line arguments by using the result of parse_known_args rather than + the previous flaky heuristics. Thanks Marc Abramowitz for tests + and initial fixing approaches in this area. + +- fix issue #479: properly handle nose/unittest(2) SkipTest exceptions + during collection/loading of test modules. Thanks to Marc Schlaich + for the complete PR. + +- fix issue490: include pytest_load_initial_conftests in documentation + and improve docstring. + +- fix issue472: clarify that ``pytest.config.getvalue()`` cannot work + if it's triggered ahead of command line parsing. + +- merge PR123: improved integration with mock.patch decorator on tests. + +- fix issue412: messing with stdout/stderr FD-level streams is now + captured without crashes. + +- fix issue483: trial/py33 works now properly. Thanks Daniel Grana for PR. + +- improve example for pytest integration with "python setup.py test" + which now has a generic "-a" or "--pytest-args" option where you + can pass additional options as a quoted string. Thanks Trevor Bekolay. + +- simplified internal capturing mechanism and made it more robust + against tests or setups changing FD1/FD2, also better integrated + now with pytest.pdb() in single tests. + +- improvements to pytest's own test-suite leakage detection, courtesy of PRs + from Marc Abramowitz + +- fix issue492: avoid leak in test_writeorg. Thanks Marc Abramowitz. + +- fix issue493: don't run tests in doc directory with ``python setup.py test`` + (use tox -e doctesting for that) + +- fix issue486: better reporting and handling of early conftest loading failures + +- some cleanup and simplification of internal conftest handling. + +- work a bit harder to break reference cycles when catching exceptions. + Thanks Jurko Gospodnetic. + +- fix issue443: fix skip examples to use proper comparison. Thanks Alex + Groenholm. + +- support nose-style ``__test__`` attribute on modules, classes and + functions, including unittest-style Classes. If set to False, the + test will not be collected. + +- fix issue512: show "<notset>" for arguments which might not be set + in monkeypatch plugin. Improves output in documentation. + + +2.5.2 (2014-01-29) +================== + +- fix issue409 -- better interoperate with cx_freeze by not + trying to import from collections.abc which causes problems + for py27/cx_freeze. Thanks Wolfgang L. for reporting and tracking it down. + +- fixed docs and code to use "pytest" instead of "py.test" almost everywhere. + Thanks Jurko Gospodnetic for the complete PR. + +- fix issue425: mention at end of "py.test -h" that --markers + and --fixtures work according to specified test path (or current dir) + +- fix issue413: exceptions with unicode attributes are now printed + correctly also on python2 and with pytest-xdist runs. (the fix + requires py-1.4.20) + +- copy, cleanup and integrate py.io capture + from pylib 1.4.20.dev2 (rev 13d9af95547e) + +- address issue416: clarify docs as to conftest.py loading semantics + +- fix issue429: comparing byte strings with non-ascii chars in assert + expressions now work better. Thanks Floris Bruynooghe. + +- make capfd/capsys.capture private, its unused and shouldn't be exposed + + +2.5.1 (2013-12-17) +================== + +- merge new documentation styling PR from Tobias Bieniek. + +- fix issue403: allow parametrize of multiple same-name functions within + a collection node. Thanks Andreas Kloeckner and Alex Gaynor for reporting + and analysis. + +- Allow parameterized fixtures to specify the ID of the parameters by + adding an ids argument to pytest.fixture() and pytest.yield_fixture(). + Thanks Floris Bruynooghe. + +- fix issue404 by always using the binary xml escape in the junitxml + plugin. Thanks Ronny Pfannschmidt. + +- fix issue407: fix addoption docstring to point to argparse instead of + optparse. Thanks Daniel D. Wright. + + + +2.5.0 (2013-12-12) +================== + +- dropped python2.5 from automated release testing of pytest itself + which means it's probably going to break soon (but still works + with this release we believe). + +- simplified and fixed implementation for calling finalizers when + parametrized fixtures or function arguments are involved. finalization + is now performed lazily at setup time instead of in the "teardown phase". + While this might sound odd at first, it helps to ensure that we are + correctly handling setup/teardown even in complex code. User-level code + should not be affected unless it's implementing the pytest_runtest_teardown + hook and expecting certain fixture instances are torn down within (very + unlikely and would have been unreliable anyway). + +- PR90: add --color=yes|no|auto option to force terminal coloring + mode ("auto" is default). Thanks Marc Abramowitz. + +- fix issue319 - correctly show unicode in assertion errors. Many + thanks to Floris Bruynooghe for the complete PR. Also means + we depend on py>=1.4.19 now. + +- fix issue396 - correctly sort and finalize class-scoped parametrized + tests independently from number of methods on the class. + +- refix issue323 in a better way -- parametrization should now never + cause Runtime Recursion errors because the underlying algorithm + for re-ordering tests per-scope/per-fixture is not recursive + anymore (it was tail-call recursive before which could lead + to problems for more than >966 non-function scoped parameters). + +- fix issue290 - there is preliminary support now for parametrizing + with repeated same values (sometimes useful to test if calling + a second time works as with the first time). + +- close issue240 - document precisely how pytest module importing + works, discuss the two common test directory layouts, and how it + interacts with PEP420-namespace packages. + +- fix issue246 fix finalizer order to be LIFO on independent fixtures + depending on a parametrized higher-than-function scoped fixture. + (was quite some effort so please bear with the complexity of this sentence :) + Thanks Ralph Schmitt for the precise failure example. + +- fix issue244 by implementing special index for parameters to only use + indices for paramentrized test ids + +- fix issue287 by running all finalizers but saving the exception + from the first failing finalizer and re-raising it so teardown will + still have failed. We reraise the first failing exception because + it might be the cause for other finalizers to fail. + +- fix ordering when mock.patch or other standard decorator-wrappings + are used with test methods. This fixues issue346 and should + help with random "xdist" collection failures. Thanks to + Ronny Pfannschmidt and Donald Stufft for helping to isolate it. + +- fix issue357 - special case "-k" expressions to allow for + filtering with simple strings that are not valid python expressions. + Examples: "-k 1.3" matches all tests parametrized with 1.3. + "-k None" filters all tests that have "None" in their name + and conversely "-k 'not None'". + Previously these examples would raise syntax errors. + +- fix issue384 by removing the trial support code + since the unittest compat enhancements allow + trial to handle it on its own + +- don't hide an ImportError when importing a plugin produces one. + fixes issue375. + +- fix issue275 - allow usefixtures and autouse fixtures + for running doctest text files. + +- fix issue380 by making --resultlog only rely on longrepr instead + of the "reprcrash" attribute which only exists sometimes. + +- address issue122: allow @pytest.fixture(params=iterator) by exploding + into a list early on. + +- fix pexpect-3.0 compatibility for pytest's own tests. + (fixes issue386) + +- allow nested parametrize-value markers, thanks James Lan for the PR. + +- fix unicode handling with new monkeypatch.setattr(import_path, value) + API. Thanks Rob Dennis. Fixes issue371. + +- fix unicode handling with junitxml, fixes issue368. + +- In assertion rewriting mode on Python 2, fix the detection of coding + cookies. See issue #330. + +- make "--runxfail" turn imperative pytest.xfail calls into no ops + (it already did neutralize pytest.mark.xfail markers) + +- refine pytest / pkg_resources interactions: The AssertionRewritingHook + PEP302 compliant loader now registers itself with setuptools/pkg_resources + properly so that the pkg_resources.resource_stream method works properly. + Fixes issue366. Thanks for the investigations and full PR to Jason R. Coombs. + +- pytestconfig fixture is now session-scoped as it is the same object during the + whole test run. Fixes issue370. + +- avoid one surprising case of marker malfunction/confusion:: + + @pytest.mark.some(lambda arg: ...) + def test_function(): + + would not work correctly because pytest assumes @pytest.mark.some + gets a function to be decorated already. We now at least detect if this + arg is a lambda and thus the example will work. Thanks Alex Gaynor + for bringing it up. + +- xfail a test on pypy that checks wrong encoding/ascii (pypy does + not error out). fixes issue385. + +- internally make varnames() deal with classes's __init__, + although it's not needed by pytest itself atm. Also + fix caching. Fixes issue376. + +- fix issue221 - handle importing of namespace-package with no + __init__.py properly. + +- refactor internal FixtureRequest handling to avoid monkeypatching. + One of the positive user-facing effects is that the "request" object + can now be used in closures. + +- fixed version comparison in pytest.importskip(modname, minverstring) + +- fix issue377 by clarifying in the nose-compat docs that pytest + does not duplicate the unittest-API into the "plain" namespace. + +- fix verbose reporting for @mock'd test functions + +2.4.2 (2013-10-04) +================== + +- on Windows require colorama and a newer py lib so that py.io.TerminalWriter() + now uses colorama instead of its own ctypes hacks. (fixes issue365) + thanks Paul Moore for bringing it up. + +- fix "-k" matching of tests where "repr" and "attr" and other names would + cause wrong matches because of an internal implementation quirk + (don't ask) which is now properly implemented. fixes issue345. + +- avoid tmpdir fixture to create too long filenames especially + when parametrization is used (issue354) + +- fix pytest-pep8 and pytest-flakes / pytest interactions + (collection names in mark plugin was assuming an item always + has a function which is not true for those plugins etc.) + Thanks Andi Zeidler. + +- introduce node.get_marker/node.add_marker API for plugins + like pytest-pep8 and pytest-flakes to avoid the messy + details of the node.keywords pseudo-dicts. Adapted + docs. + +- remove attempt to "dup" stdout at startup as it's icky. + the normal capturing should catch enough possibilities + of tests messing up standard FDs. + +- add pluginmanager.do_configure(config) as a link to + config.do_configure() for plugin-compatibility + +2.4.1 (2013-10-02) +================== + +- When using parser.addoption() unicode arguments to the + "type" keyword should also be converted to the respective types. + thanks Floris Bruynooghe, @dnozay. (fixes issue360 and issue362) + +- fix dotted filename completion when using argcomplete + thanks Anthon van der Neuth. (fixes issue361) + +- fix regression when a 1-tuple ("arg",) is used for specifying + parametrization (the values of the parametrization were passed + nested in a tuple). Thanks Donald Stufft. + +- merge doc typo fixes, thanks Andy Dirnberger + +2.4 +=== + +known incompatibilities: + +- if calling --genscript from python2.7 or above, you only get a + standalone script which works on python2.7 or above. Use Python2.6 + to also get a python2.5 compatible version. + +- all xunit-style teardown methods (nose-style, pytest-style, + unittest-style) will not be called if the corresponding setup method failed, + see issue322 below. + +- the pytest_plugin_unregister hook wasn't ever properly called + and there is no known implementation of the hook - so it got removed. + +- pytest.fixture-decorated functions cannot be generators (i.e. use + yield) anymore. This change might be reversed in 2.4.1 if it causes + unforeseen real-life issues. However, you can always write and return + an inner function/generator and change the fixture consumer to iterate + over the returned generator. This change was done in lieu of the new + ``pytest.yield_fixture`` decorator, see below. + +new features: + +- experimentally introduce a new ``pytest.yield_fixture`` decorator + which accepts exactly the same parameters as pytest.fixture but + mandates a ``yield`` statement instead of a ``return statement`` from + fixture functions. This allows direct integration with "with-style" + context managers in fixture functions and generally avoids registering + of finalization callbacks in favour of treating the "after-yield" as + teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris + Bruynooghe, Ronny Pfannschmidt and many others for discussions. + +- allow boolean expression directly with skipif/xfail + if a "reason" is also specified. Rework skipping documentation + to recommend "condition as booleans" because it prevents surprises + when importing markers between modules. Specifying conditions + as strings will remain fully supported. + +- reporting: color the last line red or green depending if + failures/errors occurred or everything passed. thanks Christian + Theunert. + +- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no + "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut. + +- fix issue181: --pdb now also works on collect errors (and + on internal errors) . This was implemented by a slight internal + refactoring and the introduction of a new hook + ``pytest_exception_interact`` hook (see next item). + +- fix issue341: introduce new experimental hook for IDEs/terminals to + intercept debugging: ``pytest_exception_interact(node, call, report)``. + +- new monkeypatch.setattr() variant to provide a shorter + invocation for patching out classes/functions from modules: + + monkeypatch.setattr("requests.get", myfunc) + + will replace the "get" function of the "requests" module with ``myfunc``. + +- fix issue322: tearDownClass is not run if setUpClass failed. Thanks + Mathieu Agopian for the initial fix. Also make all of pytest/nose + finalizer mimic the same generic behaviour: if a setupX exists and + fails, don't run teardownX. This internally introduces a new method + "node.addfinalizer()" helper which can only be called during the setup + phase of a node. + +- simplify pytest.mark.parametrize() signature: allow to pass a + CSV-separated string to specify argnames. For example: + ``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])`` + works as well as the previous: + ``pytest.mark.parametrize(("input", "expected"), ...)``. + +- add support for setUpModule/tearDownModule detection, thanks Brian Okken. + +- integrate tab-completion on options through use of "argcomplete". + Thanks Anthon van der Neut for the PR. + +- change option names to be hyphen-separated long options but keep the + old spelling backward compatible. py.test -h will only show the + hyphenated version, for example "--collect-only" but "--collectonly" + will remain valid as well (for backward-compat reasons). Many thanks to + Anthon van der Neut for the implementation and to Hynek Schlawack for + pushing us. + +- fix issue 308 - allow to mark/xfail/skip individual parameter sets + when parametrizing. Thanks Brianna Laugher. + +- call new experimental pytest_load_initial_conftests hook to allow + 3rd party plugins to do something before a conftest is loaded. + +Bug fixes: + +- fix issue358 - capturing options are now parsed more properly + by using a new parser.parse_known_args method. + +- pytest now uses argparse instead of optparse (thanks Anthon) which + means that "argparse" is added as a dependency if installing into python2.6 + environments or below. + +- fix issue333: fix a case of bad unittest/pytest hook interaction. + +- PR27: correctly handle nose.SkipTest during collection. Thanks + Antonio Cuni, Ronny Pfannschmidt. + +- fix issue355: junitxml puts name="pytest" attribute to testsuite tag. + +- fix issue336: autouse fixture in plugins should work again. + +- fix issue279: improve object comparisons on assertion failure + for standard datatypes and recognise collections.abc. Thanks to + Brianna Laugher and Mathieu Agopian. + +- fix issue317: assertion rewriter support for the is_package method + +- fix issue335: document py.code.ExceptionInfo() object returned + from pytest.raises(), thanks Mathieu Agopian. + +- remove implicit distribute_setup support from setup.py. + +- fix issue305: ignore any problems when writing pyc files. + +- SO-17664702: call fixture finalizers even if the fixture function + partially failed (finalizers would not always be called before) + +- fix issue320 - fix class scope for fixtures when mixed with + module-level functions. Thanks Anatloy Bubenkoff. + +- you can specify "-q" or "-qq" to get different levels of "quieter" + reporting (thanks Katarzyna Jachim) + +- fix issue300 - Fix order of conftest loading when starting py.test + in a subdirectory. + +- fix issue323 - sorting of many module-scoped arg parametrizations + +- make sessionfinish hooks execute with the same cwd-context as at + session start (helps fix plugin behaviour which write output files + with relative path such as pytest-cov) + +- fix issue316 - properly reference collection hooks in docs + +- fix issue 306 - cleanup of -k/-m options to only match markers/test + names/keywords respectively. Thanks Wouter van Ackooy. + +- improved doctest counting for doctests in python modules -- + files without any doctest items will not show up anymore + and doctest examples are counted as separate test items. + thanks Danilo Bellini. + +- fix issue245 by depending on the released py-1.4.14 + which fixes py.io.dupfile to work with files with no + mode. Thanks Jason R. Coombs. + +- fix junitxml generation when test output contains control characters, + addressing issue267, thanks Jaap Broekhuizen + +- fix issue338: honor --tb style for setup/teardown errors as well. Thanks Maho. + +- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin. + +- better parametrize error messages, thanks Brianna Laugher + +- pytest_terminal_summary(terminalreporter) hooks can now use + ".section(title)" and ".line(msg)" methods to print extra + information at the end of a test run. + +2.3.5 (2013-04-30) +================== + +- fix issue169: respect --tb=style with setup/teardown errors as well. + +- never consider a fixture function for test function collection + +- allow re-running of test items / helps to fix pytest-reruntests plugin + and also help to keep less fixture/resource references alive + +- put captured stdout/stderr into junitxml output even for passing tests + (thanks Adam Goucher) + +- Issue 265 - integrate nose setup/teardown with setupstate + so it doesn't try to teardown if it did not setup + +- issue 271 - don't write junitxml on worker nodes + +- Issue 274 - don't try to show full doctest example + when doctest does not know the example location + +- issue 280 - disable assertion rewriting on buggy CPython 2.6.0 + +- inject "getfixture()" helper to retrieve fixtures from doctests, + thanks Andreas Zeidler + +- issue 259 - when assertion rewriting, be consistent with the default + source encoding of ASCII on Python 2 + +- issue 251 - report a skip instead of ignoring classes with init + +- issue250 unicode/str mixes in parametrization names and values now works + +- issue257, assertion-triggered compilation of source ending in a + comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6) + +- fix --genscript option to generate standalone scripts that also + work with python3.3 (importer ordering) + +- issue171 - in assertion rewriting, show the repr of some + global variables + +- fix option help for "-k" + +- move long description of distribution into README.rst + +- improve docstring for metafunc.parametrize() + +- fix bug where using capsys with pytest.set_trace() in a test + function would break when looking at capsys.readouterr() + +- allow to specify prefixes starting with "_" when + customizing python_functions test discovery. (thanks Graham Horler) + +- improve PYTEST_DEBUG tracing output by putting + extra data on a new lines with additional indent + +- ensure OutcomeExceptions like skip/fail have initialized exception attributes + +- issue 260 - don't use nose special setup on plain unittest cases + +- fix issue134 - print the collect errors that prevent running specified test items + +- fix issue266 - accept unicode in MarkEvaluator expressions + +2.3.4 (2012-11-20) +================== + +- yielded test functions will now have autouse-fixtures active but + cannot accept fixtures as funcargs - it's anyway recommended to + rather use the post-2.0 parametrize features instead of yield, see: + http://pytest.org/en/stable/example/parametrize.html +- fix autouse-issue where autouse-fixtures would not be discovered + if defined in an a/conftest.py file and tests in a/tests/test_some.py +- fix issue226 - LIFO ordering for fixture teardowns +- fix issue224 - invocations with >256 char arguments now work +- fix issue91 - add/discuss package/directory level setups in example +- allow to dynamically define markers via + item.keywords[...]=assignment integrating with "-m" option +- make "-k" accept an expressions the same as with "-m" so that one + can write: -k "name1 or name2" etc. This is a slight incompatibility + if you used special syntax like "TestClass.test_method" which you now + need to write as -k "TestClass and test_method" to match a certain + method in a certain test class. + +2.3.3 (2012-11-06) +================== + +- fix issue214 - parse modules that contain special objects like e. g. + flask's request object which blows up on getattr access if no request + is active. thanks Thomas Waldmann. + +- fix issue213 - allow to parametrize with values like numpy arrays that + do not support an __eq__ operator + +- fix issue215 - split test_python.org into multiple files + +- fix issue148 - @unittest.skip on classes is now recognized and avoids + calling setUpClass/tearDownClass, thanks Pavel Repin + +- fix issue209 - reintroduce python2.4 support by depending on newer + pylib which re-introduced statement-finding for pre-AST interpreters + +- nose support: only call setup if it's a callable, thanks Andrew + Taumoefolau + +- fix issue219 - add py2.4-3.3 classifiers to TROVE list + +- in tracebacks *,** arg values are now shown next to normal arguments + (thanks Manuel Jacob) + +- fix issue217 - support mock.patch with pytest's fixtures - note that + you need either mock-1.0.1 or the python3.3 builtin unittest.mock. + +- fix issue127 - improve documentation for pytest_addoption() and + add a ``config.getoption(name)`` helper function for consistency. + +2.3.2 (2012-10-25) +================== + +- fix issue208 and fix issue29 use new py version to avoid long pauses + when printing tracebacks in long modules + +- fix issue205 - conftests in subdirs customizing + pytest_pycollect_makemodule and pytest_pycollect_makeitem + now work properly + +- fix teardown-ordering for parametrized setups + +- fix issue127 - better documentation for pytest_addoption + and related objects. + +- fix unittest behaviour: TestCase.runtest only called if there are + test methods defined + +- improve trial support: don't collect its empty + unittest.TestCase.runTest() method + +- "python setup.py test" now works with pytest itself + +- fix/improve internal/packaging related bits: + + - exception message check of test_nose.py now passes on python33 as well + + - issue206 - fix test_assertrewrite.py to work when a global + PYTHONDONTWRITEBYTECODE=1 is present + + - add tox.ini to pytest distribution so that ignore-dirs and others config + bits are properly distributed for maintainers who run pytest-own tests + +2.3.1 (2012-10-20) +================== + +- fix issue202 - fix regression: using "self" from fixture functions now + works as expected (it's the same "self" instance that a test method + which uses the fixture sees) + +- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems + due to pexpect not supporting it properly (hanging) + +- link to web pages from --markers output which provides help for + pytest.mark.* usage. + +2.3.0 (2012-10-19) +================== + +- fix issue202 - better automatic names for parametrized test functions +- fix issue139 - introduce @pytest.fixture which allows direct scoping + and parametrization of funcarg factories. +- fix issue198 - conftest fixtures were not found on windows32 in some + circumstances with nested directory structures due to path manipulation issues +- fix issue193 skip test functions with were parametrized with empty + parameter sets +- fix python3.3 compat, mostly reporting bits that previously depended + on dict ordering +- introduce re-ordering of tests by resource and parametrization setup + which takes precedence to the usual file-ordering +- fix issue185 monkeypatching time.time does not cause pytest to fail +- fix issue172 duplicate call of pytest.fixture decoratored setup_module + functions +- fix junitxml=path construction so that if tests change the + current working directory and the path is a relative path + it is constructed correctly from the original current working dir. +- fix "python setup.py test" example to cause a proper "errno" return +- fix issue165 - fix broken doc links and mention stackoverflow for FAQ +- catch unicode-issues when writing failure representations + to terminal to prevent the whole session from crashing +- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip + will now take precedence before xfail-markers because we + can't determine xfail/xpass status in case of a skip. see also: + http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get + +- always report installed 3rd party plugins in the header of a test run + +- fix issue160: a failing setup of an xfail-marked tests should + be reported as xfail (not xpass) + +- fix issue128: show captured output when capsys/capfd are used + +- fix issue179: properly show the dependency chain of factories + +- pluginmanager.register(...) now raises ValueError if the + plugin has been already registered or the name is taken + +- fix issue159: improve https://docs.pytest.org/en/6.0.1/faq.html + especially with respect to the "magic" history, also mention + pytest-django, trial and unittest integration. + +- make request.keywords and node.keywords writable. All descendant + collection nodes will see keyword values. Keywords are dictionaries + containing markers and other info. + +- fix issue 178: xml binary escapes are now wrapped in py.xml.raw + +- fix issue 176: correctly catch the builtin AssertionError + even when we replaced AssertionError with a subclass on the + python level + +- factory discovery no longer fails with magic global callables + that provide no sane __code__ object (mock.call for example) + +- fix issue 182: testdir.inprocess_run now considers passed plugins + +- fix issue 188: ensure sys.exc_info is clear on python2 + before calling into a test + +- fix issue 191: add unittest TestCase runTest method support +- fix issue 156: monkeypatch correctly handles class level descriptors + +- reporting refinements: + + - pytest_report_header now receives a "startdir" so that + you can use startdir.bestrelpath(yourpath) to show + nice relative path + + - allow plugins to implement both pytest_report_header and + pytest_sessionstart (sessionstart is invoked first). + + - don't show deselected reason line if there is none + + - py.test -vv will show all of assert comparisons instead of truncating + +2.2.4 (2012-05-22) +================== + +- fix error message for rewritten assertions involving the % operator +- fix issue 126: correctly match all invalid xml characters for junitxml + binary escape +- fix issue with unittest: now @unittest.expectedFailure markers should + be processed correctly (you can also use @pytest.mark markers) +- document integration with the extended distribute/setuptools test commands +- fix issue 140: properly get the real functions + of bound classmethods for setup/teardown_class +- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net +- fix issue #143: call unconfigure/sessionfinish always when + configure/sessionstart where called +- fix issue #144: better mangle test ids to junitxml classnames +- upgrade distribute_setup.py to 0.6.27 + +2.2.3 (2012-02-05) +================== + +- fix uploaded package to only include necessary files + +2.2.2 (2012-02-05) +================== + +- fix issue101: wrong args to unittest.TestCase test function now + produce better output +- fix issue102: report more useful errors and hints for when a + test directory was renamed and some pyc/__pycache__ remain +- fix issue106: allow parametrize to be applied multiple times + e.g. from module, class and at function level. +- fix issue107: actually perform session scope finalization +- don't check in parametrize if indirect parameters are funcarg names +- add chdir method to monkeypatch funcarg +- fix crash resulting from calling monkeypatch undo a second time +- fix issue115: make --collectonly robust against early failure + (missing files/directories) +- "-qq --collectonly" now shows only files and the number of tests in them +- "-q --collectonly" now shows test ids +- allow adding of attributes to test reports such that it also works + with distributed testing (no upgrade of pytest-xdist needed) + +2.2.1 (2011-12-16) +================== + +- fix issue99 (in pytest and py) internallerrors with resultlog now + produce better output - fixed by normalizing pytest_internalerror + input arguments. +- fix issue97 / traceback issues (in pytest and py) improve traceback output + in conjunction with jinja2 and cython which hack tracebacks +- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns": + the final test in a test node will now run its teardown directly + instead of waiting for the end of the session. Thanks Dave Hunt for + the good reporting and feedback. The pytest_runtest_protocol as well + as the pytest_runtest_teardown hooks now have "nextitem" available + which will be None indicating the end of the test run. +- fix collection crash due to unknown-source collected items, thanks + to Ralf Schmitt (fixed by depending on a more recent pylib) + +2.2.0 (2011-11-18) +================== + +- fix issue90: introduce eager tearing down of test items so that + teardown function are called earlier. +- add an all-powerful metafunc.parametrize function which allows to + parametrize test function arguments in multiple steps and therefore + from independent plugins and places. +- add a @pytest.mark.parametrize helper which allows to easily + call a test function with different argument values +- Add examples to the "parametrize" example page, including a quick port + of Test scenarios and the new parametrize function and decorator. +- introduce registration for "pytest.mark.*" helpers via ini-files + or through plugin hooks. Also introduce a "--strict" option which + will treat unregistered markers as errors + allowing to avoid typos and maintain a well described set of markers + for your test suite. See exaples at http://pytest.org/en/stable/mark.html + and its links. +- issue50: introduce "-m marker" option to select tests based on markers + (this is a stricter and more predictable version of '-k' in that "-m" + only matches complete markers and has more obvious rules for and/or + semantics. +- new feature to help optimizing the speed of your tests: + --durations=N option for displaying N slowest test calls + and setup/teardown methods. +- fix issue87: --pastebin now works with python3 +- fix issue89: --pdb with unexpected exceptions in doctest work more sensibly +- fix and cleanup pytest's own test suite to not leak FDs +- fix issue83: link to generated funcarg list +- fix issue74: pyarg module names are now checked against imp.find_module false positives +- fix compatibility with twisted/trial-11.1.0 use cases +- simplify Node.listchain +- simplify junitxml output code by relying on py.xml +- add support for skip properties on unittest classes and functions + +2.1.3 (2011-10-18) +================== + +- fix issue79: assertion rewriting failed on some comparisons in boolops +- correctly handle zero length arguments (a la pytest '') +- fix issue67 / junitxml now contains correct test durations, thanks ronny +- fix issue75 / skipping test failure on jython +- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests + +2.1.2 (2011-09-24) +================== + +- fix assertion rewriting on files with windows newlines on some Python versions +- refine test discovery by package/module name (--pyargs), thanks Florian Mayer +- fix issue69 / assertion rewriting fixed on some boolean operations +- fix issue68 / packages now work with assertion rewriting +- fix issue66: use different assertion rewriting caches when the -O option is passed +- don't try assertion rewriting on Jython, use reinterp + +2.1.1 +===== + +- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks +- fix issue60 / fix error conditions involving the creation of __pycache__ +- fix issue63 / assertion rewriting on inserts involving strings containing '%' +- fix assertion rewriting on calls with a ** arg +- don't cache rewritten modules if bytecode generation is disabled +- fix assertion rewriting in read-only directories +- fix issue59: provide system-out/err tags for junitxml output +- fix issue61: assertion rewriting on boolean operations with 3 or more operands +- you can now build a man page with "cd doc ; make man" + +2.1.0 (2011-07-09) +================== + +- fix issue53 call nosestyle setup functions with correct ordering +- fix issue58 and issue59: new assertion code fixes +- merge Benjamin's assertionrewrite branch: now assertions + for test modules on python 2.6 and above are done by rewriting + the AST and saving the pyc file before the test module is imported. + see doc/assert.txt for more info. +- fix issue43: improve doctests with better traceback reporting on + unexpected exceptions +- fix issue47: timing output in junitxml for test cases is now correct +- fix issue48: typo in MarkInfo repr leading to exception +- fix issue49: avoid confusing error when initizaliation partially fails +- fix issue44: env/username expansion for junitxml file path +- show releaselevel information in test runs for pypy +- reworked doc pages for better navigation and PDF generation +- report KeyboardInterrupt even if interrupted during session startup +- fix issue 35 - provide PDF doc version and download link from index page + +2.0.3 (2011-05-11) +================== + +- fix issue38: nicer tracebacks on calls to hooks, particularly early + configure/sessionstart ones + +- fix missing skip reason/meta information in junitxml files, reported + via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html + +- fix issue34: avoid collection failure with "test" prefixed classes + deriving from object. + +- don't require zlib (and other libs) for genscript plugin without + --genscript actually being used. + +- speed up skips (by not doing a full traceback representation + internally) + +- fix issue37: avoid invalid characters in junitxml's output + +2.0.2 (2011-03-09) +================== + +- tackle issue32 - speed up test runs of very quick test functions + by reducing the relative overhead + +- fix issue30 - extended xfail/skipif handling and improved reporting. + If you have a syntax error in your skip/xfail + expressions you now get nice error reports. + + Also you can now access module globals from xfail/skipif + expressions so that this for example works now:: + + import pytest + import mymodule + @pytest.mark.skipif("mymodule.__version__[0] == "1") + def test_function(): + pass + + This will not run the test function if the module's version string + does not start with a "1". Note that specifying a string instead + of a boolean expressions allows py.test to report meaningful information + when summarizing a test run as to what conditions lead to skipping + (or xfail-ing) tests. + +- fix issue28 - setup_method and pytest_generate_tests work together + The setup_method fixture method now gets called also for + test function invocations generated from the pytest_generate_tests + hook. + +- fix issue27 - collectonly and keyword-selection (-k) now work together + Also, if you do "py.test --collectonly -q" you now get a flat list + of test ids that you can use to paste to the py.test commandline + in order to execute a particular test. + +- fix issue25 avoid reported problems with --pdb and python3.2/encodings output + +- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP + Starting with Python3.2 os.symlink may be supported. By requiring + a newer py lib version the py.path.local() implementation acknowledges + this. + +- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular + thanks to Laura Creighton who also reviewed parts of the documentation. + +- fix slightly wrong output of verbose progress reporting for classes + (thanks Amaury) + +- more precise (avoiding of) deprecation warnings for node.Class|Function accesses + +- avoid std unittest assertion helper code in tracebacks (thanks Ronny) + +2.0.1 (2011-02-07) +================== + +- refine and unify initial capturing so that it works nicely + even if the logging module is used on an early-loaded conftest.py + file or plugin. +- allow to omit "()" in test ids to allow for uniform test ids + as produced by Alfredo's nice pytest.vim plugin. +- fix issue12 - show plugin versions with "--version" and + "--traceconfig" and also document how to add extra information + to reporting test header +- fix issue17 (import-* reporting issue on python3) by + requiring py>1.4.0 (1.4.1 is going to include it) +- fix issue10 (numpy arrays truth checking) by refining + assertion interpretation in py lib +- fix issue15: make nose compatibility tests compatible + with python3 (now that nose-1.0 supports python3) +- remove somewhat surprising "same-conftest" detection because + it ignores conftest.py when they appear in several subdirs. +- improve assertions ("not in"), thanks Floris Bruynooghe +- improve behaviour/warnings when running on top of "python -OO" + (assertions and docstrings are turned off, leading to potential + false positives) +- introduce a pytest_cmdline_processargs(args) hook + to allow dynamic computation of command line arguments. + This fixes a regression because py.test prior to 2.0 + allowed to set command line options from conftest.py + files which so far pytest-2.0 only allowed from ini-files now. +- fix issue7: assert failures in doctest modules. + unexpected failures in doctests will not generally + show nicer, i.e. within the doctest failing context. +- fix issue9: setup/teardown functions for an xfail-marked + test will report as xfail if they fail but report as normally + passing (not xpassing) if they succeed. This only is true + for "direct" setup/teardown invocations because teardown_class/ + teardown_module cannot closely relate to a single test. +- fix issue14: no logging errors at process exit +- refinements to "collecting" output on non-ttys +- refine internal plugin registration and --traceconfig output +- introduce a mechanism to prevent/unregister plugins from the + command line, see http://pytest.org/en/stable/plugins.html#cmdunregister +- activate resultlog plugin by default +- fix regression wrt yielded tests which due to the + collection-before-running semantics were not + setup as with pytest 1.3.4. Note, however, that + the recommended and much cleaner way to do test + parametraization remains the "pytest_generate_tests" + mechanism, see the docs. + +2.0.0 (2010-11-25) +================== + +- pytest-2.0 is now its own package and depends on pylib-2.0 +- new ability: python -m pytest / python -m pytest.main ability +- new python invocation: pytest.main(args, plugins) to load + some custom plugins early. +- try harder to run unittest test suites in a more compatible manner + by deferring setup/teardown semantics to the unittest package. + also work harder to run twisted/trial and Django tests which + should now basically work by default. +- introduce a new way to set config options via ini-style files, + by default setup.cfg and tox.ini files are searched. The old + ways (certain environment variables, dynamic conftest.py reading + is removed). +- add a new "-q" option which decreases verbosity and prints a more + nose/unittest-style "dot" output. +- fix issue135 - marks now work with unittest test cases as well +- fix issue126 - introduce py.test.set_trace() to trace execution via + PDB during the running of tests even if capturing is ongoing. +- fix issue123 - new "python -m py.test" invocation for py.test + (requires Python 2.5 or above) +- fix issue124 - make reporting more resilient against tests opening + files on filedescriptor 1 (stdout). +- fix issue109 - sibling conftest.py files will not be loaded. + (and Directory collectors cannot be customized anymore from a Directory's + conftest.py - this needs to happen at least one level up). +- introduce (customizable) assertion failure representations and enhance + output on assertion failures for comparisons and other cases (Floris Bruynooghe) +- nose-plugin: pass through type-signature failures in setup/teardown + functions instead of not calling them (Ed Singleton) +- remove py.test.collect.Directory (follows from a major refactoring + and simplification of the collection process) +- majorly reduce py.test core code, shift function/python testing to own plugin +- fix issue88 (finding custom test nodes from command line arg) +- refine 'tmpdir' creation, will now create basenames better associated + with test names (thanks Ronny) +- "xpass" (unexpected pass) tests don't cause exitcode!=0 +- fix issue131 / issue60 - importing doctests in __init__ files used as namespace packages +- fix issue93 stdout/stderr is captured while importing conftest.py +- fix bug: unittest collected functions now also can have "pytestmark" + applied at class/module level +- add ability to use "class" level for cached_setup helper +- fix strangeness: mark.* objects are now immutable, create new instances + +1.3.4 (2010-09-14) +================== + +- fix issue111: improve install documentation for windows +- fix issue119: fix custom collectability of __init__.py as a module +- fix issue116: --doctestmodules work with __init__.py files as well +- fix issue115: unify internal exception passthrough/catching/GeneratorExit +- fix issue118: new --tb=native for presenting cpython-standard exceptions + +1.3.3 (2010-07-30) +================== + +- fix issue113: assertion representation problem with triple-quoted strings + (and possibly other cases) +- make conftest loading detect that a conftest file with the same + content was already loaded, avoids surprises in nested directory structures + which can be produced e.g. by Hudson. It probably removes the need to use + --confcutdir in most cases. +- fix terminal coloring for win32 + (thanks Michael Foord for reporting) +- fix weirdness: make terminal width detection work on stdout instead of stdin + (thanks Armin Ronacher for reporting) +- remove trailing whitespace in all py/text distribution files + +1.3.2 (2010-07-08) +================== + +**New features** + +- fix issue103: introduce py.test.raises as context manager, examples:: + + with py.test.raises(ZeroDivisionError): + x = 0 + 1 / x + + with py.test.raises(RuntimeError) as excinfo: + call_something() + + # you may do extra checks on excinfo.value|type|traceback here + + (thanks Ronny Pfannschmidt) + +- Funcarg factories can now dynamically apply a marker to a + test invocation. This is for example useful if a factory + provides parameters to a test which are expected-to-fail:: + + def pytest_funcarg__arg(request): + request.applymarker(py.test.mark.xfail(reason="flaky config")) + ... + + def test_function(arg): + ... + +- improved error reporting on collection and import errors. This makes + use of a more general mechanism, namely that for custom test item/collect + nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can + override it to return a string error representation of your choice + which is going to be reported as a (red) string. + +- introduce '--junitprefix=STR' option to prepend a prefix + to all reports in the junitxml file. + +**Bug fixes** + +- make tests and the ``pytest_recwarn`` plugin in particular fully compatible + to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that + you can properly check for their existence in a cross-python manner). +- refine --pdb: ignore xfailed tests, unify its TB-reporting and + don't display failures again at the end. +- fix assertion interpretation with the ** operator (thanks Benjamin Peterson) +- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson) +- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous) +- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny) +- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson) +- fix py.code.compile(source) to generate unique filenames +- fix assertion re-interp problems on PyPy, by defering code + compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot) +- fix py.path.local.pyimport() to work with directories +- streamline py.path.local.mkdtemp implementation and usage +- don't print empty lines when showing junitxml-filename +- add optional boolean ignore_errors parameter to py.path.local.remove +- fix terminal writing on win32/python2.4 +- py.process.cmdexec() now tries harder to return properly encoded unicode objects + on all python versions +- install plain py.test/py.which scripts also for Jython, this helps to + get canonical script paths in virtualenv situations +- make path.bestrelpath(path) return ".", note that when calling + X.bestrelpath the assumption is that X is a directory. +- make initial conftest discovery ignore "--" prefixed arguments +- fix resultlog plugin when used in a multicpu/multihost xdist situation + (thanks Jakub Gustak) +- perform distributed testing related reporting in the xdist-plugin + rather than having dist-related code in the generic py.test + distribution +- fix homedir detection on Windows +- ship distribute_setup.py version 0.6.13 + +1.3.1 (2010-05-25) +================== + +**New features** + +- issue91: introduce new py.test.xfail(reason) helper + to imperatively mark a test as expected to fail. Can + be used from within setup and test functions. This is + useful especially for parametrized tests when certain + configurations are expected-to-fail. In this case the + declarative approach with the @py.test.mark.xfail cannot + be used as it would mark all configurations as xfail. + +- issue102: introduce new --maxfail=NUM option to stop + test runs after NUM failures. This is a generalization + of the '-x' or '--exitfirst' option which is now equivalent + to '--maxfail=1'. Both '-x' and '--maxfail' will + now also print a line near the end indicating the Interruption. + +- issue89: allow py.test.mark decorators to be used on classes + (class decorators were introduced with python2.6) and + also allow to have multiple markers applied at class/module level + by specifying a list. + +- improve and refine letter reporting in the progress bar: + . pass + f failed test + s skipped tests (reminder: use for dependency/platform mismatch only) + x xfailed test (test that was expected to fail) + X xpassed test (test that was expected to fail but passed) + + You can use any combination of 'fsxX' with the '-r' extended + reporting option. The xfail/xpass results will show up as + skipped tests in the junitxml output - which also fixes + issue99. + +- make py.test.cmdline.main() return the exitstatus instead of raising + SystemExit and also allow it to be called multiple times. This of + course requires that your application and tests are properly teared + down and don't have global state. + +**Bug Fixes** + +- improved traceback presentation: + - improved and unified reporting for "--tb=short" option + - Errors during test module imports are much shorter, (using --tb=short style) + - raises shows shorter more relevant tracebacks + - --fulltrace now more systematically makes traces longer / inhibits cutting + +- improve support for raises and other dynamically compiled code by + manipulating python's linecache.cache instead of the previous + rather hacky way of creating custom code objects. This makes + it seemlessly work on Jython and PyPy where it previously didn't. + +- fix issue96: make capturing more resilient against Control-C + interruptions (involved somewhat substantial refactoring + to the underlying capturing functionality to avoid race + conditions). + +- fix chaining of conditional skipif/xfail decorators - so it works now + as expected to use multiple @py.test.mark.skipif(condition) decorators, + including specific reporting which of the conditions lead to skipping. + +- fix issue95: late-import zlib so that it's not required + for general py.test startup. + +- fix issue94: make reporting more robust against bogus source code + (and internally be more careful when presenting unexpected byte sequences) + + +1.3.0 (2010-05-05) +================== + +- deprecate --report option in favour of a new shorter and easier to + remember -r option: it takes a string argument consisting of any + combination of 'xfsX' characters. They relate to the single chars + you see during the dotted progress printing and will print an extra line + per test at the end of the test run. This extra line indicates the exact + position or test ID that you directly paste to the py.test cmdline in order + to re-run a particular test. + +- allow external plugins to register new hooks via the new + pytest_addhooks(pluginmanager) hook. The new release of + the pytest-xdist plugin for distributed and looponfailing + testing requires this feature. + +- add a new pytest_ignore_collect(path, config) hook to allow projects and + plugins to define exclusion behaviour for their directory structure - + for example you may define in a conftest.py this method:: + + def pytest_ignore_collect(path): + return path.check(link=1) + + to prevent even a collection try of any tests in symlinked dirs. + +- new pytest_pycollect_makemodule(path, parent) hook for + allowing customization of the Module collection object for a + matching test module. + +- extend and refine xfail mechanism: + ``@py.test.mark.xfail(run=False)`` do not run the decorated test + ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries + specifying ``--runxfail`` on command line virtually ignores xfail markers + +- expose (previously internal) commonly useful methods: + py.io.get_terminal_with() -> return terminal width + py.io.ansi_print(...) -> print colored/bold text on linux/win32 + py.io.saferepr(obj) -> return limited representation string + +- expose test outcome related exceptions as py.test.skip.Exception, + py.test.raises.Exception etc., useful mostly for plugins + doing special outcome interpretation/tweaking + +- (issue85) fix junitxml plugin to handle tests with non-ascii output + +- fix/refine python3 compatibility (thanks Benjamin Peterson) + +- fixes for making the jython/win32 combination work, note however: + jython2.5.1/win32 does not provide a command line launcher, see + http://bugs.jython.org/issue1491 . See pylib install documentation + for how to work around. + +- fixes for handling of unicode exception values and unprintable objects + +- (issue87) fix unboundlocal error in assertionold code + +- (issue86) improve documentation for looponfailing + +- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method + +- ship distribute_setup.py version 0.6.10 + +- added links to the new capturelog and coverage plugins + + +1.2.0 (2010-01-18) +================== + +- refined usage and options for "py.cleanup":: + + py.cleanup # remove "*.pyc" and "*$py.class" (jython) files + py.cleanup -e .swp -e .cache # also remove files with these extensions + py.cleanup -s # remove "build" and "dist" directory next to setup.py files + py.cleanup -d # also remove empty directories + py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'" + py.cleanup -n # dry run, only show what would be removed + +- add a new option "py.test --funcargs" which shows available funcargs + and their help strings (docstrings on their respective factory function) + for a given test path + +- display a short and concise traceback if a funcarg lookup fails + +- early-load "conftest.py" files in non-dot first-level sub directories. + allows to conveniently keep and access test-related options in a ``test`` + subdir and still add command line options. + +- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value + +- fix issue78: always call python-level teardown functions even if the + according setup failed. This includes refinements for calling setup_module/class functions + which will now only be called once instead of the previous behaviour where they'd be called + multiple times if they raise an exception (including a Skipped exception). Any exception + will be re-corded and associated with all tests in the according module/class scope. + +- fix issue63: assume <40 columns to be a bogus terminal width, default to 80 + +- fix pdb debugging to be in the correct frame on raises-related errors + +- update apipkg.py to fix an issue where recursive imports might + unnecessarily break importing + +- fix plugin links + +1.1.1 (2009-11-24) +================== + +- moved dist/looponfailing from py.test core into a new + separately released pytest-xdist plugin. + +- new junitxml plugin: --junitxml=path will generate a junit style xml file + which is processable e.g. by the Hudson CI system. + +- new option: --genscript=path will generate a standalone py.test script + which will not need any libraries installed. thanks to Ralf Schmitt. + +- new option: --ignore will prevent specified path from collection. + Can be specified multiple times. + +- new option: --confcutdir=dir will make py.test only consider conftest + files that are relative to the specified dir. + +- new funcarg: "pytestconfig" is the pytest config object for access + to command line args and can now be easily used in a test. + +- install ``py.test`` and ``py.which`` with a ``-$VERSION`` suffix to + disambiguate between Python3, python2.X, Jython and PyPy installed versions. + +- new "pytestconfig" funcarg allows access to test config object + +- new "pytest_report_header" hook can return additional lines + to be displayed at the header of a test run. + +- (experimental) allow "py.test path::name1::name2::..." for pointing + to a test within a test collection directly. This might eventually + evolve as a full substitute to "-k" specifications. + +- streamlined plugin loading: order is now as documented in + customize.html: setuptools, ENV, commandline, conftest. + also setuptools entry point names are turned to canonical names ("pytest_*") + +- automatically skip tests that need 'capfd' but have no os.dup + +- allow pytest_generate_tests to be defined in classes as well + +- deprecate usage of 'disabled' attribute in favour of pytestmark +- deprecate definition of Directory, Module, Class and Function nodes + in conftest.py files. Use pytest collect hooks instead. + +- collection/item node specific runtest/collect hooks are only called exactly + on matching conftest.py files, i.e. ones which are exactly below + the filesystem path of an item + +- change: the first pytest_collect_directory hook to return something + will now prevent further hooks to be called. + +- change: figleaf plugin now requires --figleaf to run. Also + change its long command line options to be a bit shorter (see py.test -h). + +- change: pytest doctest plugin is now enabled by default and has a + new option --doctest-glob to set a pattern for file matches. + +- change: remove internal py._* helper vars, only keep py._pydir + +- robustify capturing to survive if custom pytest_runtest_setup + code failed and prevented the capturing setup code from running. + +- make py.test.* helpers provided by default plugins visible early - + works transparently both for pydoc and for interactive sessions + which will regularly see e.g. py.test.mark and py.test.importorskip. + +- simplify internal plugin manager machinery +- simplify internal collection tree by introducing a RootCollector node + +- fix assert reinterpreation that sees a call containing "keyword=..." + +- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish + hooks on worker nodes during dist-testing, report module/session teardown + hooks correctly. + +- fix issue65: properly handle dist-testing if no + execnet/py lib installed remotely. + +- skip some install-tests if no execnet is available + +- fix docs, fix internal bin/ script generation + + +1.1.0 (2009-11-05) +================== + +- introduce automatic plugin registration via 'pytest11' + entrypoints via setuptools' pkg_resources.iter_entry_points + +- fix py.test dist-testing to work with execnet >= 1.0.0b4 + +- re-introduce py.test.cmdline.main() for better backward compatibility + +- svn paths: fix a bug with path.check(versioned=True) for svn paths, + allow '%' in svn paths, make svnwc.update() default to interactive mode + like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction. + +- refine distributed tarball to contain test and no pyc files + +- try harder to have deprecation warnings for py.compat.* accesses + report a correct location + +1.0.3 +===== + +* adjust and improve docs + +* remove py.rest tool and internal namespace - it was + never really advertised and can still be used with + the old release if needed. If there is interest + it could be revived into its own tool i guess. + +* fix issue48 and issue59: raise an Error if the module + from an imported test file does not seem to come from + the filepath - avoids "same-name" confusion that has + been reported repeatedly + +* merged Ronny's nose-compatibility hacks: now + nose-style setup_module() and setup() functions are + supported + +* introduce generalized py.test.mark function marking + +* reshuffle / refine command line grouping + +* deprecate parser.addgroup in favour of getgroup which creates option group + +* add --report command line option that allows to control showing of skipped/xfailed sections + +* generalized skipping: a new way to mark python functions with skipif or xfail + at function, class and modules level based on platform or sys-module attributes. + +* extend py.test.mark decorator to allow for positional args + +* introduce and test "py.cleanup -d" to remove empty directories + +* fix issue #59 - robustify unittest test collection + +* make bpython/help interaction work by adding an __all__ attribute + to ApiModule, cleanup initpkg + +* use MIT license for pylib, add some contributors + +* remove py.execnet code and substitute all usages with 'execnet' proper + +* fix issue50 - cached_setup now caches more to expectations + for test functions with multiple arguments. + +* merge Jarko's fixes, issue #45 and #46 + +* add the ability to specify a path for py.lookup to search in + +* fix a funcarg cached_setup bug probably only occurring + in distributed testing and "module" scope with teardown. + +* many fixes and changes for making the code base python3 compatible, + many thanks to Benjamin Peterson for helping with this. + +* consolidate builtins implementation to be compatible with >=2.3, + add helpers to ease keeping 2 and 3k compatible code + +* deprecate py.compat.doctest|subprocess|textwrap|optparse + +* deprecate py.magic.autopath, remove py/magic directory + +* move pytest assertion handling to py/code and a pytest_assertion + plugin, add "--no-assert" option, deprecate py.magic namespaces + in favour of (less) py.code ones. + +* consolidate and cleanup py/code classes and files + +* cleanup py/misc, move tests to bin-for-dist + +* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg + +* consolidate py.log implementation, remove old approach. + +* introduce py.io.TextIO and py.io.BytesIO for distinguishing between + text/unicode and byte-streams (uses underlying standard lib io.* + if available) + +* make py.unittest_convert helper script available which converts "unittest.py" + style files into the simpler assert/direct-test-classes py.test/nosetests + style. The script was written by Laura Creighton. + +* simplified internal localpath implementation + +1.0.2 (2009-08-27) +================== + +* fixing packaging issues, triggered by fedora redhat packaging, + also added doc, examples and contrib dirs to the tarball. + +* added a documentation link to the new django plugin. + +1.0.1 (2009-08-19) +================== + +* added a 'pytest_nose' plugin which handles nose.SkipTest, + nose-style function/method/generator setup/teardown and + tries to report functions correctly. + +* capturing of unicode writes or encoded strings to sys.stdout/err + work better, also terminalwriting was adapted and somewhat + unified between windows and linux. + +* improved documentation layout and content a lot + +* added a "--help-config" option to show conftest.py / ENV-var names for + all longopt cmdline options, and some special conftest.py variables. + renamed 'conf_capture' conftest setting to 'option_capture' accordingly. + +* fix issue #27: better reporting on non-collectable items given on commandline + (e.g. pyc files) + +* fix issue #33: added --version flag (thanks Benjamin Peterson) + +* fix issue #32: adding support for "incomplete" paths to wcpath.status() + +* "Test" prefixed classes are *not* collected by default anymore if they + have an __init__ method + +* monkeypatch setenv() now accepts a "prepend" parameter + +* improved reporting of collection error tracebacks + +* simplified multicall mechanism and plugin architecture, + renamed some internal methods and argnames + +1.0.0 (2009-08-04) +================== + +* more terse reporting try to show filesystem path relatively to current dir +* improve xfail output a bit + +1.0.0b9 (2009-07-31) +==================== + +* cleanly handle and report final teardown of test setup + +* fix svn-1.6 compat issue with py.path.svnwc().versioned() + (thanks Wouter Vanden Hove) + +* setup/teardown or collection problems now show as ERRORs + or with big "E"'s in the progress lines. they are reported + and counted separately. + +* dist-testing: properly handle test items that get locally + collected but cannot be collected on the remote side - often + due to platform/dependency reasons + +* simplified py.test.mark API - see keyword plugin documentation + +* integrate better with logging: capturing now by default captures + test functions and their immediate setup/teardown in a single stream + +* capsys and capfd funcargs now have a readouterr() and a close() method + (underlyingly py.io.StdCapture/FD objects are used which grew a + readouterr() method as well to return snapshots of captured out/err) + +* make assert-reinterpretation work better with comparisons not + returning bools (reported with numpy from thanks maciej fijalkowski) + +* reworked per-test output capturing into the pytest_iocapture.py plugin + and thus removed capturing code from config object + +* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr) + + +1.0.0b8 (2009-07-22) +==================== + +* pytest_unittest-plugin is now enabled by default + +* introduced pytest_keyboardinterrupt hook and + refined pytest_sessionfinish hooked, added tests. + +* workaround a buggy logging module interaction ("closing already closed + files"). Thanks to Sridhar Ratnakumar for triggering. + +* if plugins use "py.test.importorskip" for importing + a dependency only a warning will be issued instead + of exiting the testing process. + +* many improvements to docs: + - refined funcargs doc , use the term "factory" instead of "provider" + - added a new talk/tutorial doc page + - better download page + - better plugin docstrings + - added new plugins page and automatic doc generation script + +* fixed teardown problem related to partially failing funcarg setups + (thanks MrTopf for reporting), "pytest_runtest_teardown" is now + always invoked even if the "pytest_runtest_setup" failed. + +* tweaked doctest output for docstrings in py modules, + thanks Radomir. + +1.0.0b7 +======= + +* renamed py.test.xfail back to py.test.mark.xfail to avoid + two ways to decorate for xfail + +* re-added py.test.mark decorator for setting keywords on functions + (it was actually documented so removing it was not nice) + +* remove scope-argument from request.addfinalizer() because + request.cached_setup has the scope arg. TOOWTDI. + +* perform setup finalization before reporting failures + +* apply modified patches from Andreas Kloeckner to allow + test functions to have no func_code (#22) and to make + "-k" and function keywords work (#20) + +* apply patch from Daniel Peolzleithner (issue #23) + +* resolve issue #18, multiprocessing.Manager() and + redirection clash + +* make __name__ == "__channelexec__" for remote_exec code + +1.0.0b3 (2009-06-19) +==================== + +* plugin classes are removed: one now defines + hooks directly in conftest.py or global pytest_*.py + files. + +* added new pytest_namespace(config) hook that allows + to inject helpers directly to the py.test.* namespace. + +* documented and refined many hooks + +* added new style of generative tests via + pytest_generate_tests hook that integrates + well with function arguments. + + +1.0.0b1 +======= + +* introduced new "funcarg" setup method, + see doc/test/funcarg.txt + +* introduced plugin architecture and many + new py.test plugins, see + doc/test/plugins.txt + +* teardown_method is now guaranteed to get + called after a test method has run. + +* new method: py.test.importorskip(mod,minversion) + will either import or call py.test.skip() + +* completely revised internal py.test architecture + +* new py.process.ForkedFunc object allowing to + fork execution of a function to a sub process + and getting a result back. + +XXX lots of things missing here XXX + +0.9.2 +===== + +* refined installation and metadata, created new setup.py, + now based on setuptools/ez_setup (thanks to Ralf Schmitt + for his support). + +* improved the way of making py.* scripts available in + windows environments, they are now added to the + Scripts directory as ".cmd" files. + +* py.path.svnwc.status() now is more complete and + uses xml output from the 'svn' command if available + (Guido Wesdorp) + +* fix for py.path.svn* to work with svn 1.5 + (Chris Lamb) + +* fix path.relto(otherpath) method on windows to + use normcase for checking if a path is relative. + +* py.test's traceback is better parseable from editors + (follows the filenames:LINENO: MSG convention) + (thanks to Osmo Salomaa) + +* fix to javascript-generation, "py.test --runbrowser" + should work more reliably now + +* removed previously accidentally added + py.test.broken and py.test.notimplemented helpers. + +* there now is a py.__version__ attribute + +0.9.1 +===== + +This is a fairly complete list of v0.9.1, which can +serve as a reference for developers. + +* allowing + signs in py.path.svn urls [39106] +* fixed support for Failed exceptions without excinfo in py.test [39340] +* added support for killing processes for Windows (as well as platforms that + support os.kill) in py.misc.killproc [39655] +* added setup/teardown for generative tests to py.test [40702] +* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739] +* fixed problem with calling .remove() on wcpaths of non-versioned files in + py.path [44248] +* fixed some import and inheritance issues in py.test [41480, 44648, 44655] +* fail to run greenlet tests when pypy is available, but without stackless + [45294] +* small fixes in rsession tests [45295] +* fixed issue with 2.5 type representations in py.test [45483, 45484] +* made that internal reporting issues displaying is done atomically in py.test + [45518] +* made that non-existing files are ignored by the py.lookup script [45519] +* improved exception name creation in py.test [45535] +* made that less threads are used in execnet [merge in 45539] +* removed lock required for atomic reporting issue displaying in py.test + [45545] +* removed globals from execnet [45541, 45547] +* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit + get called in 2.5 (py.execnet) [45548] +* fixed bug in joining threads in py.execnet's servemain [45549] +* refactored py.test.rsession tests to not rely on exact output format anymore + [45646] +* using repr() on test outcome [45647] +* added 'Reason' classes for py.test.skip() [45648, 45649] +* killed some unnecessary sanity check in py.test.collect [45655] +* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only + usable by Administrators [45901] +* added support for locking and non-recursive commits to py.path.svnwc [45994] +* locking files in py.execnet to prevent CPython from segfaulting [46010] +* added export() method to py.path.svnurl +* fixed -d -x in py.test [47277] +* fixed argument concatenation problem in py.path.svnwc [49423] +* restore py.test behaviour that it exits with code 1 when there are failures + [49974] +* don't fail on html files that don't have an accompanying .txt file [50606] +* fixed 'utestconvert.py < input' [50645] +* small fix for code indentation in py.code.source [50755] +* fix _docgen.py documentation building [51285] +* improved checks for source representation of code blocks in py.test [51292] +* added support for passing authentication to py.path.svn* objects [52000, + 52001] +* removed sorted() call for py.apigen tests in favour of [].sort() to support + Python 2.3 [52481] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conf.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conf.py index 39ba5e4f854..c631484aa34 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conf.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # pytest documentation build configuration file, created by # sphinx-quickstart on Fri Oct 8 17:54:28 2010. @@ -16,11 +15,15 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. -import datetime import os import sys from _pytest import __version__ as version +from _pytest.compat import TYPE_CHECKING + +if TYPE_CHECKING: + import sphinx.application + release = ".".join(version.split(".")[:2]) @@ -40,6 +43,7 @@ todo_include_todos = 1 # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ + "pallets_sphinx_themes", "pygments_pytest", "sphinx.ext.autodoc", "sphinx.ext.autosummary", @@ -63,9 +67,8 @@ source_suffix = ".rst" master_doc = "contents" # General information about the project. -project = u"pytest" -year = datetime.datetime.utcnow().year -copyright = u"2015–2020, holger krekel and pytest-dev team" +project = "pytest" +copyright = "2015–2020, holger krekel and pytest-dev team" # The language for content autogenerated by Sphinx. Refer to documentation @@ -81,7 +84,6 @@ copyright = u"2015–2020, holger krekel and pytest-dev team" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [ - "links.inc", "_build", "naming20.rst", "test/*", @@ -95,7 +97,7 @@ exclude_patterns = [ # The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None +default_role = "literal" # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True @@ -115,6 +117,19 @@ pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] +# A list of regular expressions that match URIs that should not be checked when +# doing a linkcheck. +linkcheck_ignore = [ + "https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations", + "https://blogs.msdn.microsoft.com/bharry/2017/06/28/testing-in-a-cloud-delivery-cadence/", + "http://pythontesting.net/framework/pytest-introduction/", + r"https://github.com/pytest-dev/pytest/issues/\d+", + r"https://github.com/pytest-dev/pytest/pull/\d+", +] + +# The number of worker threads to use when checking links (default=5). +linkcheck_workers = 5 + # -- Options for HTML output --------------------------------------------------- @@ -128,7 +143,7 @@ html_theme = "flask" # 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 = {"index_logo": None} +# html_theme_options = {"index_logo": None} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] @@ -147,7 +162,7 @@ html_logo = "img/pytest1.png" # 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 = "img/pytest1favi.ico" +html_favicon = "img/favicon.png" # 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, @@ -168,18 +183,18 @@ html_favicon = "img/pytest1favi.ico" html_sidebars = { "index": [ + "slim_searchbox.html", "sidebarintro.html", "globaltoc.html", "links.html", "sourcelink.html", - "searchbox.html", ], "**": [ + "slim_searchbox.html", "globaltoc.html", "relations.html", "links.html", "sourcelink.html", - "searchbox.html", ], } @@ -193,7 +208,7 @@ html_sidebars = { html_domain_indices = True # If false, no index is generated. -html_use_index = False +html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False @@ -233,8 +248,8 @@ latex_documents = [ ( "contents", "pytest.tex", - u"pytest Documentation", - u"holger krekel, trainer and consultant, http://merlinux.eu", + "pytest Documentation", + "holger krekel, trainer and consultant, http://merlinux.eu", "manual", ) ] @@ -266,16 +281,16 @@ latex_domain_indices = False # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [("usage", "pytest", u"pytest usage", [u"holger krekel at merlinux eu"], 1)] +man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)] # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. -epub_title = u"pytest" -epub_author = u"holger krekel at merlinux eu" -epub_publisher = u"holger krekel at merlinux eu" -epub_copyright = u"2013-2020, holger krekel et alii" +epub_title = "pytest" +epub_author = "holger krekel at merlinux eu" +epub_publisher = "holger krekel at merlinux eu" +epub_copyright = "2013-2020, holger krekel et alii" # The language of the text. It defaults to the language option # or en if the language is not set. @@ -329,15 +344,57 @@ texinfo_documents = [ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} +intersphinx_mapping = { + "pluggy": ("https://pluggy.readthedocs.io/en/latest", None), + "python": ("https://docs.python.org/3", None), +} + + +def configure_logging(app: "sphinx.application.Sphinx") -> None: + """Configure Sphinx's WarningHandler to handle (expected) missing include.""" + import sphinx.util.logging + import logging + class WarnLogFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + """Ignore warnings about missing include with "only" directive. -def setup(app): + Ref: https://github.com/sphinx-doc/sphinx/issues/2150.""" + if ( + record.msg.startswith('Problems with "include" directive path:') + and "_changelog_towncrier_draft.rst" in record.msg + ): + return False + return True + + logger = logging.getLogger(sphinx.util.logging.NAMESPACE) + warn_handler = [x for x in logger.handlers if x.level == logging.WARNING] + assert len(warn_handler) == 1, warn_handler + warn_handler[0].filters.insert(0, WarnLogFilter()) + + +def setup(app: "sphinx.application.Sphinx") -> None: # from sphinx.ext.autodoc import cut_lines # app.connect('autodoc-process-docstring', cut_lines(4, what=['module'])) + app.add_crossref_type( + "fixture", + "fixture", + objname="built-in fixture", + indextemplate="pair: %s; fixture", + ) + app.add_object_type( "confval", "confval", objname="configuration value", indextemplate="pair: %s; configuration value", ) + + app.add_object_type( + "globalvar", + "globalvar", + objname="global variable interpreted by pytest", + indextemplate="pair: %s; global variable interpreted by pytest", + ) + + configure_logging(app) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conftest.py index b51aae5b685..1a62e1b5df5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/conftest.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- collect_ignore = ["conf.py"] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst index 1ec502c1f2b..58a08744ced 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/contents.rst @@ -42,15 +42,19 @@ Full pytest documentation backwards-compatibility deprecations py27-py34-deprecation - historical-notes - license + contributing development_guide + + sponsor + tidelift + license + contact + + historical-notes talks projects - faq - contact - tidelift + .. only:: html diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/customize.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/customize.rst index 77217e9d2e3..9f7c365dc45 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/customize.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/customize.rst @@ -14,83 +14,183 @@ configurations files by using the general help option: This will display command line and configuration file settings which were registered by installed plugins. -.. _rootdir: -.. _inifiles: +.. _`config file formats`: + +Configuration file formats +-------------------------- + +Many :ref:`pytest settings <ini options ref>` can be set in a *configuration file*, which +by convention resides on the root of your repository or in your +tests folder. + +A quick example of the configuration files supported by pytest: + +pytest.ini +~~~~~~~~~~ + +``pytest.ini`` files take precedence over other files, even when empty. + +.. code-block:: ini + + # pytest.ini + [pytest] + minversion = 6.0 + addopts = -ra -q + testpaths = + tests + integration + + +pyproject.toml +~~~~~~~~~~~~~~ + +.. versionadded:: 6.0 + +``pyproject.toml`` are considered for configuration when they contain a ``tool.pytest.ini_options`` table. + +.. code-block:: toml + + # pyproject.toml + [tool.pytest.ini_options] + minversion = "6.0" + addopts = "-ra -q" + testpaths = [ + "tests", + "integration", + ] + +.. note:: + + One might wonder why ``[tool.pytest.ini_options]`` instead of ``[tool.pytest]`` as is the + case with other tools. -Initialization: determining rootdir and inifile ------------------------------------------------ + The reason is that the pytest team intends to fully utilize the rich TOML data format + for configuration in the future, reserving the ``[tool.pytest]`` table for that. + The ``ini_options`` table is being used, for now, as a bridge between the existing + ``.ini`` configuration system and the future configuration format. +tox.ini +~~~~~~~ +``tox.ini`` files are the configuration files of the `tox <https://tox.readthedocs.io>`__ project, +and can also be used to hold pytest configuration if they have a ``[pytest]`` section. + +.. code-block:: ini + + # tox.ini + [pytest] + minversion = 6.0 + addopts = -ra -q + testpaths = + tests + integration + + +setup.cfg +~~~~~~~~~ + +``setup.cfg`` files are general purpose configuration files, used originally by `distutils <https://docs.python.org/3/distutils/configfile.html>`__, and can also be used to hold pytest configuration +if they have a ``[tool:pytest]`` section. + +.. code-block:: ini + + # setup.cfg + [tool:pytest] + minversion = 6.0 + addopts = -ra -q + testpaths = + tests + integration + +.. warning:: + + Usage of ``setup.cfg`` is not recommended unless for very simple use cases. ``.cfg`` + files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track + down problems. + When possible, it is recommended to use the latter files, or ``pyproject.toml``, to hold your + pytest configuration. + + +.. _rootdir: +.. _configfiles: + +Initialization: determining rootdir and configfile +-------------------------------------------------- pytest determines a ``rootdir`` for each test run which depends on the command line arguments (specified test files, paths) and on -the existence of *ini-files*. The determined ``rootdir`` and *ini-file* are +the existence of configuration files. The determined ``rootdir`` and ``configfile`` are printed as part of the pytest header during startup. Here's a summary what ``pytest`` uses ``rootdir`` for: * Construct *nodeids* during collection; each test is assigned - a unique *nodeid* which is rooted at the ``rootdir`` and takes in account full path, - class name, function name and parametrization (if any). + a unique *nodeid* which is rooted at the ``rootdir`` and takes into account + the full path, class name, function name and parametrization (if any). * Is used by plugins as a stable location to store project/test run specific information; for example, the internal :ref:`cache <cache>` plugin creates a ``.pytest_cache`` subdirectory in ``rootdir`` to store its cross-test run state. -Important to emphasize that ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or +``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or influence how modules are imported. See :ref:`pythonpath` for more details. -``--rootdir=path`` command-line option can be used to force a specific directory. -The directory passed may contain environment variables when it is used in conjunction -with ``addopts`` in a ``pytest.ini`` file. +The ``--rootdir=path`` command-line option can be used to force a specific directory. +Note that contrary to other command-line options, ``--rootdir`` cannot be used with +:confval:`addopts` inside ``pytest.ini`` because the ``rootdir`` is used to *find* ``pytest.ini`` +already. Finding the ``rootdir`` ~~~~~~~~~~~~~~~~~~~~~~~ Here is the algorithm which finds the rootdir from ``args``: -- determine the common ancestor directory for the specified ``args`` that are +- Determine the common ancestor directory for the specified ``args`` that are recognised as paths that exist in the file system. If no such paths are found, the common ancestor directory is set to the current working directory. -- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor - directory and upwards. If one is matched, it becomes the ini-file and its - directory becomes the rootdir. +- Look for ``pytest.ini``, ``pyproject.toml``, ``tox.ini``, and ``setup.cfg`` files in the ancestor + directory and upwards. If one is matched, it becomes the ``configfile`` and its + directory becomes the ``rootdir``. -- if no ini-file was found, look for ``setup.py`` upwards from the common +- If no configuration file was found, look for ``setup.py`` upwards from the common ancestor directory to determine the ``rootdir``. -- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and +- If no ``setup.py`` was found, look for ``pytest.ini``, ``pyproject.toml``, ``tox.ini``, and ``setup.cfg`` in each of the specified ``args`` and upwards. If one is - matched, it becomes the ini-file and its directory becomes the rootdir. + matched, it becomes the ``configfile`` and its directory becomes the ``rootdir``. -- if no ini-file was found, use the already determined common ancestor as root +- If no ``configfile`` was found, use the already determined common ancestor as root directory. This allows the use of pytest in structures that are not part of - a package and don't have any particular ini-file configuration. + a package and don't have any particular configuration file. If no ``args`` are given, pytest collects test below the current working -directory and also starts determining the rootdir from there. +directory and also starts determining the ``rootdir`` from there. -:warning: custom pytest plugin commandline arguments may include a path, as in - ``pytest --log-output ../../test.log args``. Then ``args`` is mandatory, - otherwise pytest uses the folder of test.log for rootdir determination - (see also `issue 1435 <https://github.com/pytest-dev/pytest/issues/1435>`_). - A dot ``.`` for referencing to the current working directory is also - possible. +Files will only be matched for configuration if: + +* ``pytest.ini``: will always match and take precedence, even if empty. +* ``pyproject.toml``: contains a ``[tool.pytest.ini_options]`` table. +* ``tox.ini``: contains a ``[pytest]`` section. +* ``setup.cfg``: contains a ``[tool:pytest]`` section. -Note that an existing ``pytest.ini`` file will always be considered a match, -whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a -``[pytest]`` or ``[tool:pytest]`` section, respectively. Options from multiple ini-files candidates are never -merged - the first one wins (``pytest.ini`` always wins, even if it does not -contain a ``[pytest]`` section). +The files are considered in the order above. Options from multiple ``configfiles`` candidates +are never merged - the first match wins. -The ``config`` object will subsequently carry these attributes: +The internal :class:`Config <_pytest.config.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture) +will subsequently carry these attributes: -- ``config.rootdir``: the determined root directory, guaranteed to exist. +- :attr:`config.rootpath <_pytest.config.Config.rootpath>`: the determined root directory, guaranteed to exist. -- ``config.inifile``: the determined ini-file, may be ``None``. +- :attr:`config.inipath <_pytest.config.Config.inipath>`: the determined ``configfile``, may be ``None`` + (it is named ``inipath`` for historical reasons). -The rootdir is used as a reference directory for constructing test +.. versionadded:: 6.1 + The ``config.rootpath`` and ``config.inipath`` properties. They are :class:`pathlib.Path` + versions of the older ``config.rootdir`` and ``config.inifile``, which have type + ``py.path.local``, and still exist for backward compatibility. + +The ``rootdir`` is used as a reference directory for constructing test addresses ("nodeids") and can be used also by plugins for storing per-testrun information. @@ -101,70 +201,36 @@ Example: pytest path/to/testdir path/other/ will determine the common ancestor as ``path`` and then -check for ini-files as follows: +check for configuration files as follows: .. code-block:: text # first look for pytest.ini files path/pytest.ini - path/setup.cfg # must also contain [tool:pytest] section to match - path/tox.ini # must also contain [pytest] section to match + path/pyproject.toml # must contain a [tool.pytest.ini_options] table to match + path/tox.ini # must contain [pytest] section to match + path/setup.cfg # must contain [tool:pytest] section to match pytest.ini - ... # all the way down to the root + ... # all the way up to the root # now look for setup.py path/setup.py setup.py - ... # all the way down to the root - - -.. _`how to change command line options defaults`: -.. _`adding default options`: - - - -How to change command line options defaults ------------------------------------------------- - -It can be tedious to type the same series of command line options -every time you use ``pytest``. For example, if you always want to see -detailed info on skipped and xfailed tests, as well as have terser "dot" -progress output, you can write it into a configuration file: - -.. code-block:: ini - - # content of pytest.ini or tox.ini - # setup.cfg files should use [tool:pytest] section instead - [pytest] - addopts = -ra -q + ... # all the way up to the root -Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command -line options while the environment is in use: -.. code-block:: bash - - export PYTEST_ADDOPTS="-v" - -Here's how the command-line is built in the presence of ``addopts`` or the environment variable: - -.. code-block:: text - - <pytest.ini:addopts> $PYTEST_ADDOPTS <extra command-line arguments> - -So if the user executes in the command-line: - -.. code-block:: bash - - pytest -m slow +.. warning:: -The actual command line executed is: - -.. code-block:: bash + Custom pytest plugin commandline arguments may include a path, as in + ``pytest --log-output ../../test.log args``. Then ``args`` is mandatory, + otherwise pytest uses the folder of test.log for rootdir determination + (see also `issue 1435 <https://github.com/pytest-dev/pytest/issues/1435>`_). + A dot ``.`` for referencing to the current working directory is also + possible. - pytest -ra -q -v -m slow -Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example -above will show verbose output because ``-v`` overwrites ``-q``. +.. _`how to change command line options defaults`: +.. _`adding default options`: Builtin configuration file options diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst index a505c0a94ac..14d1eeb98af 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/deprecations.rst @@ -16,15 +16,201 @@ Deprecated Features ------------------- Below is a complete list of all pytest features which are considered deprecated. Using those features will issue -:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using -:ref:`standard warning filters <warnings>`. +:class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`. + + +The ``pytest_warning_captured`` hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 + +This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``. + +Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter +by a ``nodeid`` parameter. + +The ``pytest.collect`` module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 + +The ``pytest.collect`` module is no longer part of the public API, all its names +should now be imported from ``pytest`` directly instead. + + +The ``pytest._fillfuncargs`` function +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 6.0 + +This function was kept for backward compatibility with an older plugin. + +It's functionality is not meant to be used directly, but if you must replace +it, use `function._request._fillfixtures()` instead, though note this is not +a public API and may break in the future. + + +Removed Features +---------------- + +As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after +an appropriate period of deprecation has passed. + +``--no-print-logs`` command-line option +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 5.4 +.. versionremoved:: 6.0 + + +The ``--no-print-logs`` option and ``log_print`` ini setting are removed. If +you used them, please use ``--show-capture`` instead. + +A ``--show-capture`` command-line option was added in ``pytest 3.5.0`` which allows to specify how to +display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). + + + +Result log (``--result-log``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 4.0 +.. versionremoved:: 6.0 + +The ``--result-log`` option produces a stream of test reports which can be +analysed at runtime, but it uses a custom format which requires users to implement their own +parser. + +The `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin provides a ``--report-log`` option, a more standard and extensible alternative, producing +one JSON object per-line, and should cover the same use cases. Please try it out and provide feedback. + +The ``pytest-reportlog`` plugin might even be merged into the core +at some point, depending on the plans for the plugins and number of users using it. + +``pytest_collect_directory`` hook +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 6.0 + +The ``pytest_collect_directory`` has not worked properly for years (it was called +but the results were ignored). Users may consider using :func:`pytest_collection_modifyitems <_pytest.hookspec.pytest_collection_modifyitems>` instead. + +TerminalReporter.writer +~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 6.0 + +The ``TerminalReporter.writer`` attribute has been deprecated and should no longer be used. This +was inadvertently exposed as part of the public API of that plugin and ties it too much +with ``py.io.TerminalWriter``. + +Plugins that used ``TerminalReporter.writer`` directly should instead use ``TerminalReporter`` +methods that provide the same functionality. + +``junit_family`` default value change to "xunit2" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionchanged:: 6.0 + +The default value of ``junit_family`` option will change to ``xunit2`` in pytest 6.0, which +is an update of the old ``xunit1`` format and is supported by default in modern tools +that manipulate this type of file (for example, Jenkins, Azure Pipelines, etc.). + +Users are recommended to try the new ``xunit2`` format and see if their tooling that consumes the JUnit +XML file supports it. + +To use the new format, update your ``pytest.ini``: + +.. code-block:: ini + + [pytest] + junit_family=xunit2 + +If you discover that your tooling does not support the new format, and want to keep using the +legacy version, set the option to ``legacy`` instead: + +.. code-block:: ini + + [pytest] + junit_family=legacy + +By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading to +pytest 6.0, where the default format will be ``xunit2``. + +In order to let users know about the transition, pytest will issue a warning in case +the ``--junitxml`` option is given in the command line but ``junit_family`` is not explicitly +configured in ``pytest.ini``. + +Services known to support the ``xunit2`` format: + +* `Jenkins <https://www.jenkins.io/>`__ with the `JUnit <https://plugins.jenkins.io/junit>`__ plugin. +* `Azure Pipelines <https://azure.microsoft.com/en-us/services/devops/pipelines>`__. + +Node Construction changed to ``Node.from_parent`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionchanged:: 6.0 + +The construction of nodes now should use the named constructor ``from_parent``. +This limitation in api surface intends to enable better/simpler refactoring of the collection tree. + +This means that instead of :code:`MyItem(name="foo", parent=collector, obj=42)` +one now has to invoke :code:`MyItem.from_parent(collector, name="foo")`. + +Plugins that wish to support older versions of pytest and suppress the warning can use +`hasattr` to check if `from_parent` exists in that version: + +.. code-block:: python + + def pytest_pycollect_makeitem(collector, name, obj): + if hasattr(MyItem, "from_parent"): + item = MyItem.from_parent(collector, name="foo") + item.obj = 42 + return item + else: + return MyItem(name="foo", parent=collector, obj=42) + +Note that ``from_parent`` should only be called with keyword arguments for the parameters. + + +``pytest.fixture`` arguments are keyword only +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 6.0 + +Passing arguments to pytest.fixture() as positional arguments has been removed - pass them by keyword instead. + +``funcargnames`` alias for ``fixturenames`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 6.0 + +The ``FixtureRequest``, ``Metafunc``, and ``Function`` classes track the names of +their associated fixtures, with the aptly-named ``fixturenames`` attribute. + +Prior to pytest 2.3, this attribute was named ``funcargnames``, and we have kept +that as an alias since. It is finally due for removal, as it is often confusing +in places where we or plugin authors must distinguish between fixture names and +names supplied by non-fixture things such as ``pytest.mark.parametrize``. + + +``pytest.config`` global +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionremoved:: 5.0 + +The ``pytest.config`` global object is deprecated. Instead use +``request.config`` (via the ``request`` fixture) or if you are a plugin author +use the ``pytest_configure(config)`` hook. Note that many hooks can also access +the ``config`` object indirectly, through ``session.config`` or ``item.config`` for example. + .. _`raises message deprecated`: ``"message"`` parameter of ``pytest.raises`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 4.1 +.. versionremoved:: 5.0 It is a common mistake to think this parameter will match the exception message, while in fact it only serves to provide a custom message in case the ``pytest.raises`` check fails. To prevent @@ -55,22 +241,12 @@ If you still have concerns about this deprecation and future removal, please com `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__. -``pytest.config`` global -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. deprecated:: 4.1 - -The ``pytest.config`` global object is deprecated. Instead use -``request.config`` (via the ``request`` fixture) or if you are a plugin author -use the ``pytest_configure(config)`` hook. Note that many hooks can also access -the ``config`` object indirectly, through ``session.config`` or ``item.config`` for example. - .. _raises-warns-exec: ``raises`` / ``warns`` with a string as the second argument ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. deprecated:: 4.1 +.. versionremoved:: 5.0 Use the context manager form of these instead. When necessary, invoke ``exec`` directly. @@ -102,26 +278,6 @@ Becomes: - - -Result log (``--result-log``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - - -The ``--resultlog`` command line option has been deprecated: it is little used -and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_. - -This feature will be effectively removed in pytest 4.0 as the team intends to include a better alternative in the core. - -If you have any concerns, please don't hesitate to `open an issue <https://github.com/pytest-dev/pytest/issues>`__. - -Removed Features ----------------- - -As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after -an appropriate period of deprecation has passed. - Using ``Class`` in custom Collectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -219,7 +375,7 @@ Metafunc.addcall .. versionremoved:: 4.0 -:meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use +``_pytest.python.Metafunc.addcall`` was a precursor to the current parametrized mechanism. Users should use :meth:`_pytest.python.Metafunc.parametrize` instead. Example: @@ -266,7 +422,7 @@ This should be updated to make use of standard fixture mechanisms: session.close() -You can consult `funcarg comparison section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_ for +You can consult `funcarg comparison section in the docs <https://docs.pytest.org/en/stable/funcarg_compare.html>`_ for more information. @@ -275,7 +431,7 @@ pytest_plugins in non-top-level conftest files .. versionremoved:: 4.0 -Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py +Defining :globalvar:`pytest_plugins` is now deprecated in non-top-level conftest.py files because they will activate referenced plugins *globally*, which is surprising because for all other pytest features ``conftest.py`` files are only *active* for tests at or below it. @@ -439,7 +595,9 @@ Internal classes accessed through ``Node`` .. versionremoved:: 4.0 Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue -this warning:: +this warning: + +.. code-block:: text usage of Function.Module is deprecated, please use pytest.Module instead @@ -452,7 +610,7 @@ This has been documented as deprecated for years, but only now we are actually e .. versionremoved:: 4.0 -As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See +As part of a large :ref:`marker-revamp`, ``_pytest.nodes.Node.get_marker`` is removed. See :ref:`the documentation <update marker code>` on tips on how to update your code. @@ -496,40 +654,3 @@ As a stopgap measure, plugin authors may still inject their names into pytest's def pytest_configure(): pytest.my_symbol = MySymbol() - - - - -Reinterpretation mode (``--assert=reinterp``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionremoved:: 3.0 - -Reinterpretation mode has now been removed and only plain and rewrite -mode are available, consequently the ``--assert=reinterp`` option is -no longer available. This also means files imported from plugins or -``conftest.py`` will not benefit from improved assertions by -default, you should use ``pytest.register_assert_rewrite()`` to -explicitly turn on assertion rewriting for those files. - -Removed command-line options -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionremoved:: 3.0 - -The following deprecated commandline options were removed: - -* ``--genscript``: no longer supported; -* ``--no-assert``: use ``--assert=plain`` instead; -* ``--nomagic``: use ``--assert=plain`` instead; -* ``--report``: use ``-r`` instead; - -py.test-X* entry points -~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionremoved:: 3.0 - -Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points -were never documented and a leftover from a pre-virtualenv era. These entry -points also created broken entry points in wheels, so removing them also -removes a source of confusion for users. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/development_guide.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/development_guide.rst index 649419316d1..77076d4834e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/development_guide.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/development_guide.rst @@ -2,59 +2,6 @@ Development Guide ================= -Some general guidelines regarding development in pytest for maintainers and contributors. Nothing here -is set in stone and can't be changed, feel free to suggest improvements or changes in the workflow. - - -Code Style ----------- - -* `PEP-8 <https://www.python.org/dev/peps/pep-0008>`_ -* `flake8 <https://pypi.org/project/flake8/>`_ for quality checks -* `invoke <http://www.pyinvoke.org/>`_ to automate development tasks - - -Branches --------- - -We have two long term branches: - -* ``master``: contains the code for the next bugfix release. -* ``features``: contains the code with new features for the next minor release. - -The official repository usually does not contain topic branches, developers and contributors should create topic -branches in their own forks. - -Exceptions can be made for cases where more than one contributor is working on the same -topic or where it makes sense to use some automatic capability of the main repository, such as automatic docs from -`readthedocs <readthedocs.org>`_ for a branch dealing with documentation refactoring. - -Issues ------- - -Any question, feature, bug or proposal is welcome as an issue. Users are encouraged to use them whenever they need. - -GitHub issues should use labels to categorize them. Labels should be created sporadically, to fill a niche; we should -avoid creating labels just for the sake of creating them. - -Each label should include a description in the GitHub's interface stating its purpose. - -Labels are managed using `labels <https://github.com/hackebrot/labels>`_. All the labels in the repository -are kept in ``.github/labels.toml``, so any changes should be via PRs to that file. -After a PR is accepted and merged, one of the maintainers must manually synchronize the labels file with the -GitHub repository. - -Temporary labels -~~~~~~~~~~~~~~~~ - -To classify issues for a special event it is encouraged to create a temporary label. This helps those involved to find -the relevant issues to work on. Examples of that are sprints in Python events or global hacking events. - -* ``temporary: EP2017 sprint``: candidate issues or PRs tackled during the EuroPython 2017 - -Issues created at those events should have other relevant labels added as well. - -Those labels should be removed after they are no longer relevant. - - -.. include:: ../../HOWTORELEASE.rst +The contributing guidelines are to be found :ref:`here <contributing>`. +The release procedure for pytest is documented on +`GitHub <https://github.com/pytest-dev/pytest/blob/master/RELEASING.rst>`_. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/doctest.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/doctest.rst index 2cb70af7258..1963214f7a4 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/doctest.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/doctest.rst @@ -8,7 +8,7 @@ can change the pattern by issuing: .. code-block:: bash - pytest --doctest-glob='*.rst' + pytest --doctest-glob="*.rst" on the command line. ``--doctest-glob`` can be given multiple times in the command-line. @@ -29,14 +29,14 @@ then you can just invoke ``pytest`` directly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item test_example.txt . [100%] - ========================= 1 passed in 0.12 seconds ========================= + ============================ 1 passed in 0.12s ============================= By default, pytest will collect ``test*.txt`` files looking for doctest directives, but you can pass additional globs using the ``--doctest-glob`` option (multi-allowed). @@ -58,7 +58,7 @@ and functions, including from test modules: $ pytest --doctest-modules =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items @@ -66,7 +66,7 @@ and functions, including from test modules: mymodule.py . [ 50%] test_example.txt . [100%] - ========================= 2 passed in 0.12 seconds ========================= + ============================ 2 passed in 0.12s ============================= You can make these changes permanent in your project by putting them into a pytest.ini file like this: @@ -103,7 +103,7 @@ that will be used for those doctest files using the Using 'doctest' options ----------------------- -The standard ``doctest`` module provides some `options <https://docs.python.org/3/library/doctest.html#option-flags>`__ +Python's standard ``doctest`` module provides some `options <https://docs.python.org/3/library/doctest.html#option-flags>`__ to configure the strictness of doctest tests. In pytest, you can enable those flags using the configuration file. @@ -115,23 +115,52 @@ lengthy exception stack traces you can just write: [pytest] doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL -pytest also introduces new options to allow doctests to run in Python 2 and -Python 3 unchanged: +Alternatively, options can be enabled by an inline comment in the doc test +itself: + +.. code-block:: rst + + >>> something_that_raises() # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ValueError: ... + +pytest also introduces new options: * ``ALLOW_UNICODE``: when enabled, the ``u`` prefix is stripped from unicode - strings in expected doctest output. + strings in expected doctest output. This allows doctests to run in Python 2 + and Python 3 unchanged. -* ``ALLOW_BYTES``: when enabled, the ``b`` prefix is stripped from byte strings +* ``ALLOW_BYTES``: similarly, the ``b`` prefix is stripped from byte strings in expected doctest output. -Alternatively, options can be enabled by an inline comment in the doc test -itself: +* ``NUMBER``: when enabled, floating-point numbers only need to match as far as + the precision you have written in the expected doctest output. For example, + the following output would only need to match to 2 decimal places:: -.. code-block:: rst + >>> math.pi + 3.14 - # content of example.rst - >>> get_unicode_greeting() # doctest: +ALLOW_UNICODE - 'Hello' + If you wrote ``3.1416`` then the actual output would need to match to 4 + decimal places; and so on. + + This avoids false positives caused by limited floating-point precision, like + this:: + + Expected: + 0.233 + Got: + 0.23300000000000001 + + ``NUMBER`` also supports lists of floating-point numbers -- in fact, it + matches floating-point numbers appearing anywhere in the output, even inside + a string! This means that it may not be appropriate to enable globally in + ``doctest_optionflags`` in your configuration file. + + .. versionadded:: 5.1 + + +Continue on failure +------------------- By default, pytest would report only the first failure for a given doctest. If you want to continue the test even when you have failures, do: @@ -177,7 +206,11 @@ It is possible to use fixtures using the ``getfixture`` helper: >>> ... >>> -Also, :ref:`usefixtures` and :ref:`autouse` fixtures are supported +Note that the fixture needs to be defined in a place visible by pytest, for example a `conftest.py` +file or plugin; normal python files containing docstrings are not normally scanned for fixtures +unless explicitly configured by :confval:`python_files`. + +Also, the :ref:`usefixtures <usefixtures>` mark and fixtures marked as :ref:`autouse <autouse>` are supported when executing text doctest files. @@ -191,15 +224,21 @@ namespace in which your doctests run. It is intended to be used within your own fixtures to provide the tests that use them with context. ``doctest_namespace`` is a standard ``dict`` object into which you -place the objects you want to appear in the doctest namespace:: +place the objects you want to appear in the doctest namespace: + +.. code-block:: python # content of conftest.py import numpy + + @pytest.fixture(autouse=True) def add_np(doctest_namespace): - doctest_namespace['np'] = numpy + doctest_namespace["np"] = numpy + +which can then be used in your doctests directly: -which can then be used in your doctests directly:: +.. code-block:: python # content of numpy.py def arange(): @@ -219,7 +258,9 @@ Skipping tests dynamically .. versionadded:: 4.4 -You can use ``pytest.skip`` to dynamically skip doctests. For example:: +You can use ``pytest.skip`` to dynamically skip doctests. For example: + +.. code-block:: text >>> import sys, pytest >>> if sys.platform.startswith('win'): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py index ba910ef6a88..a172a007e8c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -import _pytest._code import pytest from pytest import raises @@ -21,7 +19,7 @@ def test_generative(param1, param2): assert param1 * 2 < param2 -class TestFailing(object): +class TestFailing: def test_simple(self): def f(): return 42 @@ -41,7 +39,7 @@ class TestFailing(object): assert not f() -class TestSpecialisedExplanations(object): +class TestSpecialisedExplanations: def test_eq_text(self): assert "spam" == "eggs" @@ -101,7 +99,7 @@ class TestSpecialisedExplanations(object): from dataclasses import dataclass @dataclass - class Foo(object): + class Foo: a: int b: str @@ -113,7 +111,7 @@ class TestSpecialisedExplanations(object): import attr @attr.s - class Foo(object): + class Foo: a = attr.ib() b = attr.ib() @@ -123,7 +121,7 @@ class TestSpecialisedExplanations(object): def test_attribute(): - class Foo(object): + class Foo: b = 1 i = Foo() @@ -131,14 +129,14 @@ def test_attribute(): def test_attribute_instance(): - class Foo(object): + class Foo: b = 1 assert Foo().b == 2 def test_attribute_failure(): - class Foo(object): + class Foo: def _get_b(self): raise Exception("Failed to get attrib") @@ -149,10 +147,10 @@ def test_attribute_failure(): def test_attribute_multiple(): - class Foo(object): + class Foo: b = 1 - class Bar(object): + class Bar: b = 2 assert Foo().b == Bar().b @@ -162,13 +160,13 @@ def globf(x): return x + 1 -class TestRaises(object): +class TestRaises: def test_raises(self): s = "qwe" raises(TypeError, int, s) def test_raises_doesnt(self): - raises(IOError, int, "3") + raises(OSError, int, "3") def test_raise(self): raise ValueError("demo error") @@ -178,7 +176,7 @@ class TestRaises(object): def test_reinterpret_fails_with_print_for_the_fun_of_it(self): items = [1, 2, 3] - print("items is %r" % items) + print("items is {!r}".format(items)) a, b = items.pop() def test_some_error(self): @@ -191,19 +189,20 @@ class TestRaises(object): # thanks to Matthew Scott for this test def test_dynamic_compile_shows_nicely(): - import imp + import importlib.util import sys src = "def foo():\n assert 1 == 0\n" name = "abc-123" - module = imp.new_module(name) - code = _pytest._code.compile(src, name, "exec") + spec = importlib.util.spec_from_loader(name, loader=None) + module = importlib.util.module_from_spec(spec) + code = compile(src, name, "exec") exec(code, module.__dict__) sys.modules[name] = module module.foo() -class TestMoreErrors(object): +class TestMoreErrors: def test_complex_error(self): def f(): return 44 @@ -253,16 +252,16 @@ class TestMoreErrors(object): x = 0 -class TestCustomAssertMsg(object): +class TestCustomAssertMsg: def test_single_line(self): - class A(object): + class A: a = 1 b = 2 assert A.a == b, "A.a appears not to be b" def test_multiline(self): - class A(object): + class A: a = 1 b = 2 @@ -271,7 +270,7 @@ class TestCustomAssertMsg(object): ), "A.a appears not to be b\nor does not appear to be b\none of those" def test_custom_repr(self): - class JSON(object): + class JSON: a = 1 def __repr__(self): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py index 8e04ac2ac46..fd467f09e59 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- -import py +import os.path import pytest -mydir = py.path.local(__file__).dirpath() +mydir = os.path.dirname(__file__) def pytest_runtest_setup(item): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello_world.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello_world.py index bfccc94f712..a31a601a1ce 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello_world.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello_world.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- hello = "world" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py index 60b5e213118..eda06dfc598 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- -import py +import os.path +import shutil -failure_demo = py.path.local(__file__).dirpath("failure_demo.py") +failure_demo = os.path.join(os.path.dirname(__file__), "failure_demo.py") pytest_plugins = ("pytester",) def test_failure_demo_fails_properly(testdir): - target = testdir.tmpdir.join(failure_demo.basename) - failure_demo.copy(target) - failure_demo.copy(testdir.tmpdir.join(failure_demo.basename)) + target = testdir.tmpdir.join(os.path.basename(failure_demo)) + shutil.copy(failure_demo, target) result = testdir.runpytest(target, syspathinsert=True) result.stdout.fnmatch_lines(["*44 failed*"]) assert result.ret != 0 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py index f49d6d8ed31..0e7eded06b6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- def setup_module(module): module.TestStateFullThing.classcount = 0 -class TestStateFullThing(object): +class TestStateFullThing: def setup_class(cls): cls.classcount += 1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst index 9bf3703ce1a..2ea87006204 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/attic.rst @@ -18,7 +18,7 @@ example: specifying and selecting acceptance tests return AcceptFixture(request) - class AcceptFixture(object): + class AcceptFixture: def __init__(self, request): if not request.config.getoption("acceptance"): pytest.skip("specify -A to run acceptance tests") @@ -65,7 +65,7 @@ extend the `accept example`_ by putting this in our test module: return arg - class TestSpecialAcceptance(object): + class TestSpecialAcceptance: def test_sometest(self, accept): assert accept.tmpdir.join("special").check() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py index 1f5a961ada0..f905738c4f6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/conftest.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- collect_ignore = ["nonpython"] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/conftest.py deleted file mode 100644 index b90c774733b..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/conftest.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - - -@pytest.fixture("session") -def setup(request): - setup = CostlySetup() - yield setup - setup.finalize() - - -class CostlySetup(object): - def __init__(self): - import time - - print("performing costly setup") - time.sleep(5) - self.timecostly = 1 - - def finalize(self): - del self.timecostly diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_a/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_a/__init__.py deleted file mode 100644 index ec51c5a2b9d..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_a/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -# diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_a/test_quick.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_a/test_quick.py deleted file mode 100644 index 4f7b9f1ac7d..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_a/test_quick.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -def test_quick(setup): - pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_b/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_b/__init__.py deleted file mode 100644 index ec51c5a2b9d..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_b/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -# diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_b/test_two.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_b/test_two.py deleted file mode 100644 index 4e08bc2b69e..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/costlysetup/sub_b/test_two.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -def test_something(setup): - assert setup.timecostly == 1 - - -def test_something_more(setup): - assert setup.timecostly == 1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order.py new file mode 100644 index 00000000000..97b3e80052b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order.py @@ -0,0 +1,38 @@ +import pytest + +# fixtures documentation order example +order = [] + + +@pytest.fixture(scope="session") +def s1(): + order.append("s1") + + +@pytest.fixture(scope="module") +def m1(): + order.append("m1") + + +@pytest.fixture +def f1(f3): + order.append("f1") + + +@pytest.fixture +def f3(): + order.append("f3") + + +@pytest.fixture(autouse=True) +def a1(): + order.append("a1") + + +@pytest.fixture +def f2(): + order.append("f2") + + +def test_order(f1, m1, f2, s1): + assert order == ["s1", "m1", "a1", "f3", "f1", "f2"] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst index f63cb822a41..6876082d418 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/index.rst @@ -15,7 +15,7 @@ For basic examples, see - :doc:`../getting-started` for basic introductory examples - :ref:`assert` for basic assertion examples -- :ref:`fixtures` for basic fixture/setup examples +- :ref:`Fixtures <fixtures>` for basic fixture/setup examples - :ref:`parametrize` for basic test function parametrization - :doc:`../unittest` for basic unittest integration - :doc:`../nose` for basic nosetests integration diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst index b8d8bef0cb1..3d55f9ebb04 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/markers.rst @@ -33,7 +33,7 @@ You can "mark" a test function with custom metadata like this: pass - class TestClass(object): + class TestClass: def test_method(self): pass @@ -45,14 +45,14 @@ You can then restrict a test run to only run tests marked with ``webtest``: $ pytest -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 4 items / 3 deselected / 1 selected test_server.py::test_send_http PASSED [100%] - ================== 1 passed, 3 deselected in 0.12 seconds ================== + ===================== 1 passed, 3 deselected in 0.12s ====================== Or the inverse, running all tests except the webtest ones: @@ -60,7 +60,7 @@ Or the inverse, running all tests except the webtest ones: $ pytest -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 4 items / 1 deselected / 3 selected @@ -69,7 +69,7 @@ Or the inverse, running all tests except the webtest ones: test_server.py::test_another PASSED [ 66%] test_server.py::TestClass::test_method PASSED [100%] - ================== 3 passed, 1 deselected in 0.12 seconds ================== + ===================== 3 passed, 1 deselected in 0.12s ====================== Selecting tests based on their node ID -------------------------------------- @@ -82,14 +82,14 @@ tests based on their module, class, method, or function name: $ pytest -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 1 item test_server.py::TestClass::test_method PASSED [100%] - ========================= 1 passed in 0.12 seconds ========================= + ============================ 1 passed in 0.12s ============================= You can also select on the class: @@ -97,14 +97,14 @@ You can also select on the class: $ pytest -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 1 item test_server.py::TestClass::test_method PASSED [100%] - ========================= 1 passed in 0.12 seconds ========================= + ============================ 1 passed in 0.12s ============================= Or select multiple nodes: @@ -112,7 +112,7 @@ Or select multiple nodes: $ pytest -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 2 items @@ -120,7 +120,7 @@ Or select multiple nodes: test_server.py::TestClass::test_method PASSED [ 50%] test_server.py::test_send_http PASSED [100%] - ========================= 2 passed in 0.12 seconds ========================= + ============================ 2 passed in 0.12s ============================= .. _node-id: @@ -141,25 +141,29 @@ Or select multiple nodes: Using ``-k expr`` to select tests based on their name ------------------------------------------------------- -.. versionadded: 2.0/2.3.4 +.. versionadded:: 2.0/2.3.4 You can use the ``-k`` command line option to specify an expression which implements a substring match on the test names instead of the exact match on markers that ``-m`` provides. This makes it easy to select tests based on their names: +.. versionchanged:: 5.4 + +The expression matching is now case-insensitive. + .. code-block:: pytest $ pytest -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 4 items / 3 deselected / 1 selected test_server.py::test_send_http PASSED [100%] - ================== 1 passed, 3 deselected in 0.12 seconds ================== + ===================== 1 passed, 3 deselected in 0.12s ====================== And you can also run all tests except the ones that match the keyword: @@ -167,7 +171,7 @@ And you can also run all tests except the ones that match the keyword: $ pytest -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 4 items / 1 deselected / 3 selected @@ -176,7 +180,7 @@ And you can also run all tests except the ones that match the keyword: test_server.py::test_another PASSED [ 66%] test_server.py::TestClass::test_method PASSED [100%] - ================== 3 passed, 1 deselected in 0.12 seconds ================== + ===================== 3 passed, 1 deselected in 0.12s ====================== Or to select "http" and "quick" tests: @@ -184,7 +188,7 @@ Or to select "http" and "quick" tests: $ pytest -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 4 items / 2 deselected / 2 selected @@ -192,22 +196,15 @@ Or to select "http" and "quick" tests: test_server.py::test_send_http PASSED [ 50%] test_server.py::test_something_quick PASSED [100%] - ================== 2 passed, 2 deselected in 0.12 seconds ================== - -.. note:: + ===================== 2 passed, 2 deselected in 0.12s ====================== - If you are using expressions such as ``"X and Y"`` then both ``X`` and ``Y`` - need to be simple non-keyword names. For example, ``"pass"`` or ``"from"`` - will result in SyntaxErrors because ``"-k"`` evaluates the expression using - Python's `eval`_ function. +You can use ``and``, ``or``, ``not`` and parentheses. -.. _`eval`: https://docs.python.org/3.6/library/functions.html#eval +In addition to the test's name, ``-k`` also matches the names of the test's parents (usually, the name of the file and class it's in), +attributes set on the test function, markers applied to it or its parents and any :attr:`extra keywords <_pytest.nodes.Node.extra_keyword_matches>` +explicitly added to it or its parents. - However, if the ``"-k"`` argument is a simple string, no such restrictions - apply. Also ``"-k 'not STRING'"`` has no restrictions. You can also - specify numbers like ``"-k 1.3"`` to match tests which are parametrized - with the float ``"1.3"``. Registering markers ------------------------------------- @@ -232,17 +229,17 @@ You can ask which markers exist for your test suite - the list includes our just $ pytest --markers @pytest.mark.webtest: mark a test as a webtest. - @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings + @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/stable/warnings.html#pytest-mark-filterwarnings @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. - @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see https://docs.pytest.org/en/latest/skipping.html + @pytest.mark.skipif(condition, ..., *, reason=...): skip the given test function if any of the conditions evaluate to True. Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. See https://docs.pytest.org/en/stable/reference.html#pytest-mark-skipif - @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/latest/skipping.html + @pytest.mark.xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): mark the test function as an expected failure if any of the conditions evaluate to True. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/stable/reference.html#pytest-mark-xfail - @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/latest/parametrize.html for more info and examples. + @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/stable/parametrize.html for more info and examples. - @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/latest/fixture.html#usefixtures + @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/fixture.html#usefixtures @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @@ -278,7 +275,7 @@ its test methods: @pytest.mark.webtest - class TestClass(object): + class TestClass: def test_startup(self): pass @@ -288,38 +285,26 @@ its test methods: This is equivalent to directly applying the decorator to the two test functions. -To remain backward-compatible with Python 2.4 you can also set a -``pytestmark`` attribute on a TestClass like this: - -.. code-block:: python +To apply marks at the module level, use the :globalvar:`pytestmark` global variable:: import pytest + pytestmark = pytest.mark.webtest +or multiple markers:: - class TestClass(object): - pytestmark = pytest.mark.webtest - -or if you need to use multiple markers you can use a list: - -.. code-block:: python - - import pytest + pytestmark = [pytest.mark.webtest, pytest.mark.slowtest] - class TestClass(object): - pytestmark = [pytest.mark.webtest, pytest.mark.slowtest] +Due to legacy reasons, before class decorators were introduced, it is possible to set the +:globalvar:`pytestmark` attribute on a test class like this: -You can also set a module level marker:: +.. code-block:: python import pytest - pytestmark = pytest.mark.webtest -or multiple markers:: - pytestmark = [pytest.mark.webtest, pytest.mark.slowtest] - -in which case markers will be applied (in left-to-right order) to -all functions and methods defined in the module. + class TestClass: + pytestmark = pytest.mark.webtest .. _`marking individual tests when using parametrize`: @@ -337,7 +322,7 @@ apply a marker to an individual test instance: @pytest.mark.foo @pytest.mark.parametrize( - ("n", "expected"), [(1, 2), pytest.param((1, 3), marks=pytest.mark.bar), (2, 3)] + ("n", "expected"), [(1, 2), pytest.param(1, 3, marks=pytest.mark.bar), (2, 3)] ) def test_increment(n, expected): assert n + 1 == expected @@ -385,7 +370,7 @@ specifies via named environments: envnames = [mark.args[0] for mark in item.iter_markers(name="env")] if envnames: if item.config.getoption("-E") not in envnames: - pytest.skip("test requires env in %r" % envnames) + pytest.skip("test requires env in {!r}".format(envnames)) A test file using this local plugin: @@ -407,14 +392,14 @@ the test needs: $ pytest -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item test_someenv.py s [100%] - ======================== 1 skipped in 0.12 seconds ========================= + ============================ 1 skipped in 0.12s ============================ and here is one that specifies exactly the environment needed: @@ -422,14 +407,14 @@ and here is one that specifies exactly the environment needed: $ pytest -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item test_someenv.py . [100%] - ========================= 1 passed in 0.12 seconds ========================= + ============================ 1 passed in 0.12s ============================= The ``--markers`` option always gives you a list of available markers: @@ -438,17 +423,17 @@ The ``--markers`` option always gives you a list of available markers: $ pytest --markers @pytest.mark.env(name): mark test to run only on named environment - @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings + @pytest.mark.filterwarnings(warning): add a warning filter to the given test. see https://docs.pytest.org/en/stable/warnings.html#pytest-mark-filterwarnings @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. - @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see https://docs.pytest.org/en/latest/skipping.html + @pytest.mark.skipif(condition, ..., *, reason=...): skip the given test function if any of the conditions evaluate to True. Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. See https://docs.pytest.org/en/stable/reference.html#pytest-mark-skipif - @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/latest/skipping.html + @pytest.mark.xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): mark the test function as an expected failure if any of the conditions evaluate to True. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See https://docs.pytest.org/en/stable/reference.html#pytest-mark-xfail - @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/latest/parametrize.html for more info and examples. + @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see https://docs.pytest.org/en/stable/parametrize.html for more info and examples. - @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/latest/fixture.html#usefixtures + @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/fixture.html#usefixtures @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @@ -500,7 +485,7 @@ The output is as follows: $ pytest -q -s Mark(name='my_marker', args=(<function hello_world at 0xdeadbeef>,), kwargs={}) . - 1 passed in 0.12 seconds + 1 passed in 0.12s We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``. @@ -524,7 +509,7 @@ code you can read over all such settings. Example: @pytest.mark.glob("class", x=2) - class TestClass(object): + class TestClass: @pytest.mark.glob("function", x=3) def test_something(self): pass @@ -540,7 +525,7 @@ test function. From a conftest file we can read it like this: def pytest_runtest_setup(item): for mark in item.iter_markers(name="glob"): - print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs)) + print("glob args={} kwargs={}".format(mark.args, mark.kwargs)) sys.stdout.flush() Let's run this without capturing output and see what we get: @@ -552,7 +537,7 @@ Let's run this without capturing output and see what we get: glob args=('class',) kwargs={'x': 2} glob args=('module',) kwargs={'x': 1} . - 1 passed in 0.12 seconds + 1 passed in 0.12s marking platform specific tests with pytest -------------------------------------------------------------- @@ -579,7 +564,7 @@ for your particular platform, you could use the following plugin: supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers()) plat = sys.platform if supported_platforms and plat not in supported_platforms: - pytest.skip("cannot run on platform %s" % (plat)) + pytest.skip("cannot run on platform {}".format(plat)) then tests will be skipped if they were specified for a different platform. Let's do a little test file to show how this looks like: @@ -615,7 +600,7 @@ then you will see two tests skipped and two executed tests as expected: $ pytest -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 4 items @@ -623,8 +608,8 @@ then you will see two tests skipped and two executed tests as expected: test_plat.py s.s. [100%] ========================= short test summary info ========================== - SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux - =================== 2 passed, 2 skipped in 0.12 seconds ==================== + SKIPPED [2] conftest.py:12: cannot run on platform linux + ======================= 2 passed, 2 skipped in 0.12s ======================= Note that if you specify a platform via the marker-command line option like this: @@ -632,14 +617,14 @@ Note that if you specify a platform via the marker-command line option like this $ pytest -m linux =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 4 items / 3 deselected / 1 selected test_plat.py . [100%] - ================== 1 passed, 3 deselected in 0.12 seconds ================== + ===================== 1 passed, 3 deselected in 0.12s ====================== then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests. @@ -648,7 +633,7 @@ Automatically adding markers based on test names .. regendoc:wipe -If you a test suite where test function names indicate a certain +If you have a test suite where test function names indicate a certain type of test, you can implement a hook that automatically defines markers so that you can use the ``-m`` option with it. Let's look at this test module: @@ -696,7 +681,7 @@ We can now use the ``-m option`` to select one set: $ pytest -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 4 items / 2 deselected / 2 selected @@ -712,7 +697,10 @@ We can now use the ``-m option`` to select one set: test_module.py:8: in test_interface_complex assert 0 E assert 0 - ================== 2 failed, 2 deselected in 0.12 seconds ================== + ========================= short test summary info ========================== + FAILED test_module.py::test_interface_simple - assert 0 + FAILED test_module.py::test_interface_complex - assert 0 + ===================== 2 failed, 2 deselected in 0.12s ====================== or to select both "event" and "interface" tests: @@ -720,7 +708,7 @@ or to select both "event" and "interface" tests: $ pytest -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 4 items / 1 deselected / 3 selected @@ -740,4 +728,8 @@ or to select both "event" and "interface" tests: test_module.py:12: in test_event_simple assert 0 E assert 0 - ================== 3 failed, 1 deselected in 0.12 seconds ================== + ========================= short test summary info ========================== + FAILED test_module.py::test_interface_simple - assert 0 + FAILED test_module.py::test_interface_complex - assert 0 + FAILED test_module.py::test_event_simple - assert 0 + ===================== 3 failed, 1 deselected in 0.12s ====================== diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py index dc722aac91d..9db6879edae 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/multipython.py @@ -1,15 +1,14 @@ -# -*- coding: utf-8 -*- """ module containing a parametrized tests testing cross-python serialization via the pickle module. """ -import distutils.spawn +import shutil import subprocess import textwrap import pytest -pythonlist = ["python2.7", "python3.4", "python3.5"] +pythonlist = ["python3.5", "python3.6", "python3.7"] @pytest.fixture(params=pythonlist) @@ -23,9 +22,9 @@ def python2(request, python1): return Python(request.param, python1.picklefile) -class Python(object): +class Python: def __init__(self, version, picklefile): - self.pythonpath = distutils.spawn.find_executable(version) + self.pythonpath = shutil.which(version) if not self.pythonpath: pytest.skip("{!r} not found".format(version)) self.picklefile = picklefile @@ -70,4 +69,4 @@ class Python(object): @pytest.mark.parametrize("obj", [42, {}, {1: 3}]) def test_basic_objects(python1, python2, obj): python1.dumps(obj) - python2.load_and_is_true("obj == %s" % obj) + python2.load_and_is_true("obj == {}".format(obj)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst index 0910071c14a..464a6c6cede 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython.rst @@ -12,14 +12,14 @@ A basic example for specifying tests in Yaml files .. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py .. _`PyYAML`: https://pypi.org/project/PyYAML/ -Here is an example ``conftest.py`` (extracted from Ali Afshnars special purpose `pytest-yamlwsgi`_ plugin). This ``conftest.py`` will collect ``test*.yml`` files and will execute the yaml-formatted content as custom tests: +Here is an example ``conftest.py`` (extracted from Ali Afshar's special purpose `pytest-yamlwsgi`_ plugin). This ``conftest.py`` will collect ``test*.yaml`` files and will execute the yaml-formatted content as custom tests: .. include:: nonpython/conftest.py :literal: You can create a simple example file: -.. include:: nonpython/test_simple.yml +.. include:: nonpython/test_simple.yaml :literal: and if you installed `PyYAML`_ or a compatible YAML-parser you can @@ -27,21 +27,23 @@ now execute the test specification: .. code-block:: pytest - nonpython $ pytest test_simple.yml + nonpython $ pytest test_simple.yaml =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR/nonpython collected 2 items - test_simple.yml F. [100%] + test_simple.yaml F. [100%] ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.12 seconds ==================== + ========================= short test summary info ========================== + FAILED test_simple.yaml::hello + ======================= 1 failed, 1 passed in 0.12s ======================== .. regendoc:wipe @@ -64,20 +66,22 @@ consulted when reporting in ``verbose`` mode: nonpython $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR/nonpython collecting ... collected 2 items - test_simple.yml::hello FAILED [ 50%] - test_simple.yml::ok PASSED [100%] + test_simple.yaml::hello FAILED [ 50%] + test_simple.yaml::ok PASSED [100%] ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.12 seconds ==================== + ========================= short test summary info ========================== + FAILED test_simple.yaml::hello + ======================= 1 failed, 1 passed in 0.12s ======================== .. regendoc:wipe @@ -88,13 +92,14 @@ interesting to just look at the collection tree: nonpython $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR/nonpython collected 2 items - <Package $REGENDOC_TMPDIR/nonpython> - <YamlFile test_simple.yml> + + <Package nonpython> + <YamlFile test_simple.yaml> <YamlItem hello> <YamlItem ok> - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py index 306aa250379..6e5a5709290 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py @@ -1,47 +1,47 @@ -# -*- coding: utf-8 -*- # content of conftest.py import pytest def pytest_collect_file(parent, path): - if path.ext == ".yml" and path.basename.startswith("test"): - return YamlFile(path, parent) + if path.ext == ".yaml" and path.basename.startswith("test"): + return YamlFile.from_parent(parent, fspath=path) class YamlFile(pytest.File): def collect(self): - import yaml # we need a yaml parser, e.g. PyYAML + # We need a yaml parser, e.g. PyYAML. + import yaml raw = yaml.safe_load(self.fspath.open()) for name, spec in sorted(raw.items()): - yield YamlItem(name, self, spec) + yield YamlItem.from_parent(self, name=name, spec=spec) class YamlItem(pytest.Item): def __init__(self, name, parent, spec): - super(YamlItem, self).__init__(name, parent) + super().__init__(name, parent) self.spec = spec def runtest(self): for name, value in sorted(self.spec.items()): - # some custom test execution (dumb example follows) + # Some custom test execution (dumb example follows). if name != value: raise YamlException(self, name, value) def repr_failure(self, excinfo): - """ called when self.runtest() raises an exception. """ + """Called when self.runtest() raises an exception.""" if isinstance(excinfo.value, YamlException): return "\n".join( [ "usecase execution failed", - " spec failed: %r: %r" % excinfo.value.args[1:3], + " spec failed: {1!r}: {2!r}".format(*excinfo.value.args), " no further details known at this point.", ] ) def reportinfo(self): - return self.fspath, 0, "usecase: %s" % self.name + return self.fspath, 0, "usecase: {}".format(self.name) class YamlException(Exception): - """ custom exception for error reporting. """ + """Custom exception for error reporting.""" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yaml b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yaml new file mode 100644 index 00000000000..8e3e7a4bbc9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yaml @@ -0,0 +1,7 @@ +# test_simple.yaml +ok: + sub1: sub1 + +hello: + world: world + some: other diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yml b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yml deleted file mode 100644 index f0d8d11fc3e..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yml +++ /dev/null @@ -1,7 +0,0 @@ -# test_simple.yml -ok: - sub1: sub1 - -hello: - world: world - some: other diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst index 2ab5e3ab10c..f1c98d449fb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/parametrize.rst @@ -19,24 +19,30 @@ Generating parameters combinations, depending on command line Let's say we want to execute a test with different computation parameters and the parameter range shall be determined by a command -line argument. Let's first write a simple (do-nothing) computation test:: +line argument. Let's first write a simple (do-nothing) computation test: + +.. code-block:: python # content of test_compute.py + def test_compute(param1): assert param1 < 4 -Now we add a test configuration like this:: +Now we add a test configuration like this: + +.. code-block:: python # content of conftest.py + def pytest_addoption(parser): - parser.addoption("--all", action="store_true", - help="run all combinations") + parser.addoption("--all", action="store_true", help="run all combinations") + def pytest_generate_tests(metafunc): - if 'param1' in metafunc.fixturenames: - if metafunc.config.getoption('all'): + if "param1" in metafunc.fixturenames: + if metafunc.config.getoption("all"): end = 5 else: end = 2 @@ -48,7 +54,7 @@ This means that we only run 2 tests if we do not pass ``--all``: $ pytest -q test_compute.py .. [100%] - 2 passed in 0.12 seconds + 2 passed in 0.12s We run only two computations, so we see two dots. let's run the full monty: @@ -66,8 +72,10 @@ let's run the full monty: > assert param1 < 4 E assert 4 < 4 - test_compute.py:3: AssertionError - 1 failed, 4 passed in 0.12 seconds + test_compute.py:4: AssertionError + ========================= short test summary info ========================== + FAILED test_compute.py::test_compute[4] - assert 4 < 4 + 1 failed, 4 passed in 0.12s As expected when running the full range of ``param1`` values we'll get an error on the last one. @@ -83,7 +91,9 @@ Running pytest with ``--collect-only`` will show the generated IDs. Numbers, strings, booleans and None will have their usual string representation used in the test ID. For other objects, pytest will make a string based on -the argument name:: +the argument name: + +.. code-block:: python # content of test_time.py @@ -112,7 +122,7 @@ the argument name:: def idfn(val): if isinstance(val, (datetime,)): # note this wouldn't show any hours/minutes/seconds - return val.strftime('%Y%m%d') + return val.strftime("%Y%m%d") @pytest.mark.parametrize("a,b,expected", testdata, ids=idfn) @@ -120,12 +130,18 @@ the argument name:: diff = a - b assert diff == expected - @pytest.mark.parametrize("a,b,expected", [ - pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11), - timedelta(1), id='forward'), - pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12), - timedelta(-1), id='backward'), - ]) + + @pytest.mark.parametrize( + "a,b,expected", + [ + pytest.param( + datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1), id="forward" + ), + pytest.param( + datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1), id="backward" + ), + ], + ) def test_timedistance_v3(a, b, expected): diff = a - b assert diff == expected @@ -144,10 +160,11 @@ objects, they are still using the default pytest representation: $ pytest test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 8 items + <Module test_time.py> <Function test_timedistance_v0[a0-b0-expected0]> <Function test_timedistance_v0[a1-b1-expected1]> @@ -158,7 +175,7 @@ objects, they are still using the default pytest representation: <Function test_timedistance_v3[forward]> <Function test_timedistance_v3[backward]> - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs together with the actual data, instead of listing them separately. @@ -171,10 +188,13 @@ A quick port of "testscenarios" Here is a quick port to run tests configured with `test scenarios`_, an add-on from Robert Collins for the standard unittest framework. We only have to work a bit to construct the correct arguments for pytest's -:py:func:`Metafunc.parametrize`:: +:py:func:`Metafunc.parametrize`: + +.. code-block:: python # content of test_scenarios.py + def pytest_generate_tests(metafunc): idlist = [] argvalues = [] @@ -182,13 +202,15 @@ only have to work a bit to construct the correct arguments for pytest's idlist.append(scenario[0]) items = scenario[1].items() argnames = [x[0] for x in items] - argvalues.append(([x[1] for x in items])) + argvalues.append([x[1] for x in items]) metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class") - scenario1 = ('basic', {'attribute': 'value'}) - scenario2 = ('advanced', {'attribute': 'value2'}) - class TestSampleWithScenarios(object): + scenario1 = ("basic", {"attribute": "value"}) + scenario2 = ("advanced", {"attribute": "value2"}) + + + class TestSampleWithScenarios: scenarios = [scenario1, scenario2] def test_demo1(self, attribute): @@ -203,14 +225,14 @@ this is a fully self-contained example which you can run with: $ pytest test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 4 items test_scenarios.py .... [100%] - ========================= 4 passed in 0.12 seconds ========================= + ============================ 4 passed in 0.12s ============================= If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function: @@ -218,10 +240,11 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia $ pytest --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 4 items + <Module test_scenarios.py> <Class TestSampleWithScenarios> <Function test_demo1[basic]> @@ -229,7 +252,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia <Function test_demo1[advanced]> <Function test_demo2[advanced]> - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== Note that we told ``metafunc.parametrize()`` that your scenario values should be considered class-scoped. With pytest-2.3 this leads to a @@ -243,12 +266,16 @@ Deferring the setup of parametrized resources The parametrization of test functions happens at collection time. It is a good idea to setup expensive resources like DB connections or subprocess only when the actual test is run. -Here is a simple example how you can achieve that, first -the actual test requiring a ``db`` object:: +Here is a simple example how you can achieve that. This test +requires a ``db`` object fixture: + +.. code-block:: python # content of test_backends.py import pytest + + def test_db_initialized(db): # a dummy test if db.__class__.__name__ == "DB2": @@ -256,20 +283,27 @@ the actual test requiring a ``db`` object:: We can now add a test configuration that generates two invocations of the ``test_db_initialized`` function and also implements a factory that -creates a database object for the actual test invocations:: +creates a database object for the actual test invocations: + +.. code-block:: python # content of conftest.py import pytest + def pytest_generate_tests(metafunc): - if 'db' in metafunc.fixturenames: - metafunc.parametrize("db", ['d1', 'd2'], indirect=True) + if "db" in metafunc.fixturenames: + metafunc.parametrize("db", ["d1", "d2"], indirect=True) + - class DB1(object): + class DB1: "one database object" - class DB2(object): + + + class DB2: "alternative database object" + @pytest.fixture def db(request): if request.param == "d1": @@ -285,15 +319,16 @@ Let's first see how it looks like at collection time: $ pytest test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items + <Module test_backends.py> <Function test_db_initialized[d1]> <Function test_db_initialized[d2]> - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== And then when we run the test: @@ -312,11 +347,37 @@ And then when we run the test: > pytest.fail("deliberately failing for demo purposes") E Failed: deliberately failing for demo purposes - test_backends.py:6: Failed - 1 failed, 1 passed in 0.12 seconds + test_backends.py:8: Failed + ========================= short test summary info ========================== + FAILED test_backends.py::test_db_initialized[d2] - Failed: deliberately f... + 1 failed, 1 passed in 0.12s The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed. Our ``db`` fixture function has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase. +Indirect parametrization +--------------------------------------------------- + +Using the ``indirect=True`` parameter when parametrizing a test allows to +parametrize a test with a fixture receiving the values before passing them to a +test: + +.. code-block:: python + + import pytest + + + @pytest.fixture + def fixt(request): + return request.param * 3 + + + @pytest.mark.parametrize("fixt", ["a", "b"], indirect=True) + def test_indirect(fixt): + assert len(fixt) == 3 + +This can be used, for example, to do more expensive setup at test run time in +the fixture, rather than having to run those setup steps at collection time. + .. regendoc:wipe Apply indirect on particular arguments @@ -327,38 +388,44 @@ parameter on particular arguments. It can be done by passing list or tuple of arguments' names to ``indirect``. In the example below there is a function ``test_indirect`` which uses two fixtures: ``x`` and ``y``. Here we give to indirect the list, which contains the name of the fixture ``x``. The indirect parameter will be applied to this argument only, and the value ``a`` -will be passed to respective fixture function:: +will be passed to respective fixture function: + +.. code-block:: python # content of test_indirect_list.py import pytest - @pytest.fixture(scope='function') + + + @pytest.fixture(scope="function") def x(request): return request.param * 3 - @pytest.fixture(scope='function') + + @pytest.fixture(scope="function") def y(request): return request.param * 2 - @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x']) - def test_indirect(x,y): - assert x == 'aaa' - assert y == 'b' + + @pytest.mark.parametrize("x, y", [("a", "b")], indirect=["x"]) + def test_indirect(x, y): + assert x == "aaa" + assert y == "b" The result of this test will be successful: .. code-block:: pytest - $ pytest test_indirect_list.py --collect-only + $ pytest -v test_indirect_list.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collected 1 item - <Module test_indirect_list.py> - <Function test_indirect[a-b]> + collecting ... collected 1 item - ======================= no tests ran in 0.12 seconds ======================= + test_indirect_list.py::test_indirect[a-b] PASSED [100%] + + ============================ 1 passed in 0.12s ============================= .. regendoc:wipe @@ -370,23 +437,28 @@ Parametrizing test methods through per-class configuration Here is an example ``pytest_generate_tests`` function implementing a parametrization scheme similar to Michael Foord's `unittest -parametrizer`_ but in a lot less code:: +parametrizer`_ but in a lot less code: + +.. code-block:: python # content of ./test_parametrize.py import pytest + def pytest_generate_tests(metafunc): # called once per each test function funcarglist = metafunc.cls.params[metafunc.function.__name__] argnames = sorted(funcarglist[0]) - metafunc.parametrize(argnames, [[funcargs[name] for name in argnames] - for funcargs in funcarglist]) + metafunc.parametrize( + argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist] + ) + - class TestClass(object): + class TestClass: # a map specifying multiple argument sets for a test method params = { - 'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ], - 'test_zerodivision': [dict(a=1, b=0), ], + "test_equals": [dict(a=1, b=2), dict(a=3, b=3)], + "test_zerodivision": [dict(a=1, b=0)], } def test_equals(self, a, b): @@ -412,8 +484,10 @@ argument sets to use for each test function. Let's run it: > assert a == b E assert 1 == 2 - test_parametrize.py:18: AssertionError - 1 failed, 2 passed in 0.12 seconds + test_parametrize.py:21: AssertionError + ========================= short test summary info ========================== + FAILED test_parametrize.py::TestClass::test_equals[1-2] - assert 1 == 2 + 1 failed, 2 passed in 0.12s Indirect parametrization with multiple fixtures -------------------------------------------------------------- @@ -429,16 +503,16 @@ is to be run with different sets of arguments for its three arguments: .. literalinclude:: multipython.py -Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize): +Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (3 interpreters times 3 interpreters times 3 objects to serialize/deserialize): .. code-block:: pytest . $ pytest -rs -q multipython.py - ...ssssssssssssssssssssssss [100%] + ssssssssssss...ssssssssssss [100%] ========================= short test summary info ========================== - SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.4' not found - SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.5' not found - 3 passed, 24 skipped in 0.12 seconds + SKIPPED [12] multipython.py:29: 'python3.5' not found + SKIPPED [12] multipython.py:29: 'python3.7' not found + 3 passed, 24 skipped in 0.12s Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -447,36 +521,47 @@ If you want to compare the outcomes of several implementations of a given API, you can write test functions that receive the already imported implementations and get skipped in case the implementation is not importable/available. Let's say we have a "base" implementation and the other (possibly optimized ones) -need to provide similar results:: +need to provide similar results: + +.. code-block:: python # content of conftest.py import pytest + @pytest.fixture(scope="session") def basemod(request): return pytest.importorskip("base") + @pytest.fixture(scope="session", params=["opt1", "opt2"]) def optmod(request): return pytest.importorskip(request.param) -And then a base implementation of a simple function:: +And then a base implementation of a simple function: + +.. code-block:: python # content of base.py def func1(): return 1 -And an optimized version:: +And an optimized version: + +.. code-block:: python # content of opt1.py def func1(): return 1.0001 -And finally a little test module:: +And finally a little test module: + +.. code-block:: python # content of test_module.py + def test_func1(basemod, optmod): assert round(basemod.func1(), 3) == round(optmod.func1(), 3) @@ -487,7 +572,7 @@ If you run this with reporting for skips enabled: $ pytest -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items @@ -495,8 +580,8 @@ If you run this with reporting for skips enabled: test_module.py .s [100%] ========================= short test summary info ========================== - SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2': No module named 'opt2' - =================== 1 passed, 1 skipped in 0.12 seconds ==================== + SKIPPED [1] conftest.py:12: could not import 'opt2': No module named 'opt2' + ======================= 1 passed, 1 skipped in 0.12s ======================= You'll see that we don't have an ``opt2`` module and thus the second test run of our ``test_func1`` was skipped. A few notes: @@ -549,16 +634,16 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker: $ pytest -v -m basic =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR - collecting ... collected 17 items / 14 deselected / 3 selected + collecting ... collected 14 items / 11 deselected / 3 selected test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%] test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%] test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%] - ============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============ + =============== 2 passed, 11 deselected, 1 xfailed in 0.12s ================ As the result: @@ -579,22 +664,28 @@ Use :func:`pytest.raises` with the in which some tests raise exceptions and others do not. It is helpful to define a no-op context manager ``does_not_raise`` to serve -as a complement to ``raises``. For example:: +as a complement to ``raises``. For example: + +.. code-block:: python from contextlib import contextmanager import pytest + @contextmanager def does_not_raise(): yield - @pytest.mark.parametrize('example_input,expectation', [ - (3, does_not_raise()), - (2, does_not_raise()), - (1, does_not_raise()), - (0, pytest.raises(ZeroDivisionError)), - ]) + @pytest.mark.parametrize( + "example_input,expectation", + [ + (3, does_not_raise()), + (2, does_not_raise()), + (1, does_not_raise()), + (0, pytest.raises(ZeroDivisionError)), + ], + ) def test_division(example_input, expectation): """Test how much I know division.""" with expectation: @@ -604,14 +695,20 @@ In the example above, the first three test cases should run unexceptionally, while the fourth should raise ``ZeroDivisionError``. If you're only supporting Python 3.7+, you can simply use ``nullcontext`` -to define ``does_not_raise``:: +to define ``does_not_raise``: + +.. code-block:: python from contextlib import nullcontext as does_not_raise -Or, if you're supporting Python 3.3+ you can use:: +Or, if you're supporting Python 3.3+ you can use: + +.. code-block:: python from contextlib import ExitStack as does_not_raise -Or, if desired, you can ``pip install contextlib2`` and use:: +Or, if desired, you can ``pip install contextlib2`` and use: + +.. code-block:: python - from contextlib2 import ExitStack as does_not_raise + from contextlib2 import nullcontext as does_not_raise diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/conftest.py deleted file mode 100644 index a6b9af7a860..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/conftest.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -import sys - -import pytest - -py3 = sys.version_info[0] >= 3 - - -class DummyCollector(pytest.collect.File): - def collect(self): - return [] - - -def pytest_pycollect_makemodule(path, parent): - bn = path.basename - if "py3" in bn and not py3 or ("py2" in bn and py3): - return DummyCollector(path, parent=parent) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/test_py2.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/test_py2.py deleted file mode 100644 index 1f665086e60..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/test_py2.py +++ /dev/null @@ -1,5 +0,0 @@ -def test_exception_syntax(): - try: - 0 / 0 - except ZeroDivisionError, e: - assert e diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/test_py3.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/test_py3.py deleted file mode 100644 index 30151f914a8..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/py2py3/test_py3.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -def test_exception_syntax(): - try: - 0 / 0 - except ZeroDivisionError as e: - assert e diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.py index bbc3fe868ea..8742526a191 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # run this with $ pytest --collect-only test_collectonly.py # @@ -7,7 +6,7 @@ def test_function(): pass -class TestClass(object): +class TestClass: def test_method(self): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst index 02c12f6bc9f..c2f0348395c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst @@ -31,7 +31,7 @@ you will see that ``pytest`` only collects test-modules, which do not match the .. code-block:: pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 5 items @@ -115,15 +115,13 @@ Changing naming conventions You can configure different naming conventions by setting the :confval:`python_files`, :confval:`python_classes` and -:confval:`python_functions` configuration options. +:confval:`python_functions` in your :ref:`configuration file <config file formats>`. Here is an example: .. code-block:: ini # content of pytest.ini # Example 1: have pytest look for "check" instead of "test" - # can also be defined in tox.ini or setup.cfg file, although the section - # name in setup.cfg files should be "tool:pytest" [pytest] python_files = check_*.py python_classes = Check @@ -131,12 +129,15 @@ Here is an example: This would make ``pytest`` look for tests in files that match the ``check_* .py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods -that match ``*_check``. For example, if we have:: +that match ``*_check``. For example, if we have: + +.. code-block:: python # content of check_myapp.py - class CheckMyApp(object): + class CheckMyApp: def simple_check(self): pass + def complex_check(self): pass @@ -146,24 +147,24 @@ The test collection would look like this: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache - rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + rootdir: $REGENDOC_TMPDIR, configfile: pytest.ini collected 2 items + <Module check_myapp.py> <Class CheckMyApp> <Function simple_check> <Function complex_check> - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== You can check for multiple glob patterns by adding a space between the patterns: .. code-block:: ini # Example 2: have pytest look for files with "test" and "example" - # content of pytest.ini, tox.ini, or setup.cfg file (replace "pytest" - # with "tool:pytest" for setup.cfg) + # content of pytest.ini [pytest] python_files = test_*.py example_*.py @@ -208,17 +209,18 @@ You can always peek at the collection tree without running tests like this: . $ pytest --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache - rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + rootdir: $REGENDOC_TMPDIR, configfile: pytest.ini collected 3 items + <Module CWD/pythoncollection.py> <Function test_function> <Class TestClass> <Function test_method> <Function test_anothermethod> - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== .. _customizing-test-collection: @@ -238,7 +240,9 @@ You can easily instruct ``pytest`` to discover tests from every Python file: However, many projects will have a ``setup.py`` which they don't want to be imported. Moreover, there may files only importable by a specific python version. For such cases you can dynamically define files to be ignored by -listing them in a ``conftest.py`` file:: +listing them in a ``conftest.py`` file: + +.. code-block:: python # content of conftest.py import sys @@ -247,7 +251,9 @@ listing them in a ``conftest.py`` file:: if sys.version_info[0] > 2: collect_ignore.append("pkg/module_py2.py") -and then if you have a module file like this:: +and then if you have a module file like this: + +.. code-block:: python # content of pkg/module_py2.py def test_only_on_python2(): @@ -256,10 +262,12 @@ and then if you have a module file like this:: except Exception, e: pass -and a ``setup.py`` dummy file like this:: +and a ``setup.py`` dummy file like this: + +.. code-block:: python # content of setup.py - 0/0 # will raise exception if imported + 0 / 0 # will raise exception if imported If you run with a Python 2 interpreter then you will find the one test and will leave out the ``setup.py`` file: @@ -283,19 +291,21 @@ file will be left out: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache - rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + rootdir: $REGENDOC_TMPDIR, configfile: pytest.ini collected 0 items - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== It's also possible to ignore files based on Unix shell-style wildcards by adding -patterns to ``collect_ignore_glob``. +patterns to :globalvar:`collect_ignore_glob`. The following example ``conftest.py`` ignores the file ``setup.py`` and in addition all files that end with ``*_py2.py`` when executed with a Python 3 -interpreter:: +interpreter: + +.. code-block:: python # content of conftest.py import sys @@ -303,3 +313,12 @@ interpreter:: collect_ignore = ["setup.py"] if sys.version_info[0] > 2: collect_ignore_glob = ["*_py2.py"] + +Since Pytest 2.6, users can prevent pytest from discovering classes that start +with ``Test`` by setting a boolean ``__test__`` attribute to ``False``. + +.. code-block:: python + + # Will not be discovered as a test + class TestClass: + __test__ = False diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst index d00c9836265..f1b973f3b33 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst @@ -9,7 +9,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: assertion $ pytest failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR/assertion collected 44 items @@ -26,7 +26,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert param1 * 2 < param2 E assert (3 * 2) < 6 - failure_demo.py:21: AssertionError + failure_demo.py:19: AssertionError _________________________ TestFailing.test_simple __________________________ self = <failure_demo.TestFailing object at 0xdeadbeef> @@ -43,7 +43,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>() E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>() - failure_demo.py:32: AssertionError + failure_demo.py:30: AssertionError ____________________ TestFailing.test_simple_multiline _____________________ self = <failure_demo.TestFailing object at 0xdeadbeef> @@ -51,7 +51,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_simple_multiline(self): > otherfunc_multi(42, 6 * 9) - failure_demo.py:35: + failure_demo.py:33: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ a = 42, b = 54 @@ -60,7 +60,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert a == b E assert 42 == 54 - failure_demo.py:16: AssertionError + failure_demo.py:14: AssertionError ___________________________ TestFailing.test_not ___________________________ self = <failure_demo.TestFailing object at 0xdeadbeef> @@ -73,7 +73,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E assert not 42 E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>() - failure_demo.py:41: AssertionError + failure_demo.py:39: AssertionError _________________ TestSpecialisedExplanations.test_eq_text _________________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -81,10 +81,10 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_text(self): > assert "spam" == "eggs" E AssertionError: assert 'spam' == 'eggs' - E - spam - E + eggs + E - eggs + E + spam - failure_demo.py:46: AssertionError + failure_demo.py:44: AssertionError _____________ TestSpecialisedExplanations.test_eq_similar_text _____________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -92,12 +92,12 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_similar_text(self): > assert "foo 1 bar" == "foo 2 bar" E AssertionError: assert 'foo 1 bar' == 'foo 2 bar' - E - foo 1 bar + E - foo 2 bar E ? ^ - E + foo 2 bar + E + foo 1 bar E ? ^ - failure_demo.py:49: AssertionError + failure_demo.py:47: AssertionError ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -106,11 +106,11 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert "foo\nspam\nbar" == "foo\neggs\nbar" E AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar' E foo - E - spam - E + eggs + E - eggs + E + spam E bar - failure_demo.py:52: AssertionError + failure_demo.py:50: AssertionError ______________ TestSpecialisedExplanations.test_eq_long_text _______________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -119,15 +119,15 @@ Here is a nice run of several failures and how ``pytest`` presents things: a = "1" * 100 + "a" + "2" * 100 b = "1" * 100 + "b" + "2" * 100 > assert a == b - E AssertionError: assert '111111111111...2222222222222' == '1111111111111...2222222222222' + E AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222' E Skipping 90 identical leading characters in diff, use -v to show E Skipping 91 identical trailing characters in diff, use -v to show - E - 1111111111a222222222 + E - 1111111111b222222222 E ? ^ - E + 1111111111b222222222 + E + 1111111111a222222222 E ? ^ - failure_demo.py:57: AssertionError + failure_demo.py:55: AssertionError _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -136,7 +136,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: a = "1\n" * 100 + "a" + "2\n" * 100 b = "1\n" * 100 + "b" + "2\n" * 100 > assert a == b - E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n' + E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n' E Skipping 190 identical leading characters in diff, use -v to show E Skipping 191 identical trailing characters in diff, use -v to show E 1 @@ -147,7 +147,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E E ...Full output truncated (7 lines hidden), use '-vv' to show - failure_demo.py:62: AssertionError + failure_demo.py:60: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -158,7 +158,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E At index 2 diff: 2 != 3 E Use -v to get the full diff - failure_demo.py:65: AssertionError + failure_demo.py:63: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -171,7 +171,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E At index 100 diff: 1 != 2 E Use -v to get the full diff - failure_demo.py:70: AssertionError + failure_demo.py:68: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -189,7 +189,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E E ...Full output truncated (2 lines hidden), use '-vv' to show - failure_demo.py:73: AssertionError + failure_demo.py:71: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -207,7 +207,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E E ...Full output truncated (2 lines hidden), use '-vv' to show - failure_demo.py:76: AssertionError + failure_demo.py:74: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -218,7 +218,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E Right contains one more item: 3 E Use -v to get the full diff - failure_demo.py:79: AssertionError + failure_demo.py:77: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -227,7 +227,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert 1 in [0, 2, 3, 4, 5] E assert 1 in [0, 2, 3, 4, 5] - failure_demo.py:82: AssertionError + failure_demo.py:80: AssertionError __________ TestSpecialisedExplanations.test_not_in_text_multiline __________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -235,7 +235,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_not_in_text_multiline(self): text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail" > assert "foo" not in text - E AssertionError: assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail' + E AssertionError: assert 'foo' not in 'some multil...nand a\ntail' E 'foo' is contained here: E some multiline E text @@ -246,7 +246,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E E ...Full output truncated (2 lines hidden), use '-vv' to show - failure_demo.py:86: AssertionError + failure_demo.py:84: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -259,7 +259,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E single foo line E ? +++ - failure_demo.py:90: AssertionError + failure_demo.py:88: AssertionError _________ TestSpecialisedExplanations.test_not_in_text_single_long _________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -267,12 +267,12 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_not_in_text_single_long(self): text = "head " * 50 + "foo " + "tail " * 20 > assert "foo" not in text - E AssertionError: assert 'foo' not in 'head head head head hea...ail tail tail tail tail ' + E AssertionError: assert 'foo' not in 'head head h...l tail tail ' E 'foo' is contained here: E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? +++ - failure_demo.py:94: AssertionError + failure_demo.py:92: AssertionError ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -280,12 +280,12 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_not_in_text_single_long_term(self): text = "head " * 50 + "f" * 70 + "tail " * 20 > assert "f" * 70 not in text - E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail ' + E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head h...l tail tail ' E 'ffffffffffffffffff...fffffffffffffffffff' is contained here: E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - failure_demo.py:98: AssertionError + failure_demo.py:96: AssertionError ______________ TestSpecialisedExplanations.test_eq_dataclass _______________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -294,19 +294,25 @@ Here is a nice run of several failures and how ``pytest`` presents things: from dataclasses import dataclass @dataclass - class Foo(object): + class Foo: a: int b: str left = Foo(1, "b") right = Foo(1, "c") > assert left == right - E AssertionError: assert TestSpecialis...oo(a=1, b='b') == TestSpecialise...oo(a=1, b='c') + E AssertionError: assert TestSpecialis...oo(a=1, b='b') == TestSpecialis...oo(a=1, b='c') + E E Omitting 1 identical items, use -vv to show E Differing attributes: - E b: 'b' != 'c' + E ['b'] + E + E Drill down into differing attribute b: + E b: 'b' != 'c'... + E + E ...Full output truncated (3 lines hidden), use '-vv' to show - failure_demo.py:110: AssertionError + failure_demo.py:108: AssertionError ________________ TestSpecialisedExplanations.test_eq_attrs _________________ self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef> @@ -315,7 +321,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: import attr @attr.s - class Foo(object): + class Foo: a = attr.ib() b = attr.ib() @@ -323,15 +329,21 @@ Here is a nice run of several failures and how ``pytest`` presents things: right = Foo(1, "c") > assert left == right E AssertionError: assert Foo(a=1, b='b') == Foo(a=1, b='c') + E E Omitting 1 identical items, use -vv to show E Differing attributes: - E b: 'b' != 'c' + E ['b'] + E + E Drill down into differing attribute b: + E b: 'b' != 'c'... + E + E ...Full output truncated (3 lines hidden), use '-vv' to show - failure_demo.py:122: AssertionError + failure_demo.py:120: AssertionError ______________________________ test_attribute ______________________________ def test_attribute(): - class Foo(object): + class Foo: b = 1 i = Foo() @@ -339,11 +351,11 @@ Here is a nice run of several failures and how ``pytest`` presents things: E assert 1 == 2 E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b - failure_demo.py:130: AssertionError + failure_demo.py:128: AssertionError _________________________ test_attribute_instance __________________________ def test_attribute_instance(): - class Foo(object): + class Foo: b = 1 > assert Foo().b == 2 @@ -351,11 +363,11 @@ Here is a nice run of several failures and how ``pytest`` presents things: E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>() - failure_demo.py:137: AssertionError + failure_demo.py:135: AssertionError __________________________ test_attribute_failure __________________________ def test_attribute_failure(): - class Foo(object): + class Foo: def _get_b(self): raise Exception("Failed to get attrib") @@ -364,7 +376,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: i = Foo() > assert i.b == 2 - failure_demo.py:148: + failure_demo.py:146: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef> @@ -373,14 +385,14 @@ Here is a nice run of several failures and how ``pytest`` presents things: > raise Exception("Failed to get attrib") E Exception: Failed to get attrib - failure_demo.py:143: Exception + failure_demo.py:141: Exception _________________________ test_attribute_multiple __________________________ def test_attribute_multiple(): - class Foo(object): + class Foo: b = 1 - class Bar(object): + class Bar: b = 2 > assert Foo().b == Bar().b @@ -390,7 +402,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>() - failure_demo.py:158: AssertionError + failure_demo.py:156: AssertionError __________________________ TestRaises.test_raises __________________________ self = <failure_demo.TestRaises object at 0xdeadbeef> @@ -400,16 +412,16 @@ Here is a nice run of several failures and how ``pytest`` presents things: > raises(TypeError, int, s) E ValueError: invalid literal for int() with base 10: 'qwe' - failure_demo.py:168: ValueError + failure_demo.py:166: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ self = <failure_demo.TestRaises object at 0xdeadbeef> def test_raises_doesnt(self): - > raises(IOError, int, "3") + > raises(OSError, int, "3") E Failed: DID NOT RAISE <class 'OSError'> - failure_demo.py:171: Failed + failure_demo.py:169: Failed __________________________ TestRaises.test_raise ___________________________ self = <failure_demo.TestRaises object at 0xdeadbeef> @@ -418,7 +430,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > raise ValueError("demo error") E ValueError: demo error - failure_demo.py:174: ValueError + failure_demo.py:172: ValueError ________________________ TestRaises.test_tupleerror ________________________ self = <failure_demo.TestRaises object at 0xdeadbeef> @@ -427,18 +439,18 @@ Here is a nice run of several failures and how ``pytest`` presents things: > a, b = [1] # NOQA E ValueError: not enough values to unpack (expected 2, got 1) - failure_demo.py:177: ValueError + failure_demo.py:175: ValueError ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ self = <failure_demo.TestRaises object at 0xdeadbeef> def test_reinterpret_fails_with_print_for_the_fun_of_it(self): items = [1, 2, 3] - print("items is %r" % items) + print("items is {!r}".format(items)) > a, b = items.pop() E TypeError: cannot unpack non-iterable int object - failure_demo.py:182: TypeError + failure_demo.py:180: TypeError --------------------------- Captured stdout call --------------------------- items is [1, 2, 3] ________________________ TestRaises.test_some_error ________________________ @@ -449,29 +461,29 @@ Here is a nice run of several failures and how ``pytest`` presents things: > if namenotexi: # NOQA E NameError: name 'namenotexi' is not defined - failure_demo.py:185: NameError + failure_demo.py:183: NameError ____________________ test_dynamic_compile_shows_nicely _____________________ def test_dynamic_compile_shows_nicely(): - import imp + import importlib.util import sys src = "def foo():\n assert 1 == 0\n" name = "abc-123" - module = imp.new_module(name) - code = _pytest._code.compile(src, name, "exec") + spec = importlib.util.spec_from_loader(name, loader=None) + module = importlib.util.module_from_spec(spec) + code = compile(src, name, "exec") exec(code, module.__dict__) sys.modules[name] = module > module.foo() - failure_demo.py:203: + failure_demo.py:202: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - def foo(): - > assert 1 == 0 - E AssertionError + > ??? + E AssertionError - <0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:200>:2: AssertionError + abc-123:2: AssertionError ____________________ TestMoreErrors.test_complex_error _____________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -485,9 +497,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: > somefunc(f(), g()) - failure_demo.py:214: + failure_demo.py:213: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - failure_demo.py:12: in somefunc + failure_demo.py:10: in somefunc otherfunc(x, y) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @@ -497,7 +509,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert a == b E assert 44 == 43 - failure_demo.py:8: AssertionError + failure_demo.py:6: AssertionError ___________________ TestMoreErrors.test_z1_unpack_error ____________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -507,7 +519,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > a, b = items E ValueError: not enough values to unpack (expected 2, got 0) - failure_demo.py:218: ValueError + failure_demo.py:217: ValueError ____________________ TestMoreErrors.test_z2_type_error _____________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -517,7 +529,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: > a, b = items E TypeError: cannot unpack non-iterable int object - failure_demo.py:222: TypeError + failure_demo.py:221: TypeError ______________________ TestMoreErrors.test_startswith ______________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -530,7 +542,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E + where False = <built-in method startswith of str object at 0xdeadbeef>('456') E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith - failure_demo.py:227: AssertionError + failure_demo.py:226: AssertionError __________________ TestMoreErrors.test_startswith_nested ___________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -549,7 +561,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>() E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>() - failure_demo.py:236: AssertionError + failure_demo.py:235: AssertionError _____________________ TestMoreErrors.test_global_func ______________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -560,7 +572,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E + where False = isinstance(43, float) E + where 43 = globf(42) - failure_demo.py:239: AssertionError + failure_demo.py:238: AssertionError _______________________ TestMoreErrors.test_instance _______________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -571,7 +583,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E assert 42 != 42 E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x - failure_demo.py:243: AssertionError + failure_demo.py:242: AssertionError _______________________ TestMoreErrors.test_compare ________________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -581,7 +593,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E assert 11 < 5 E + where 11 = globf(10) - failure_demo.py:246: AssertionError + failure_demo.py:245: AssertionError _____________________ TestMoreErrors.test_try_finally ______________________ self = <failure_demo.TestMoreErrors object at 0xdeadbeef> @@ -592,13 +604,13 @@ Here is a nice run of several failures and how ``pytest`` presents things: > assert x == 0 E assert 1 == 0 - failure_demo.py:251: AssertionError + failure_demo.py:250: AssertionError ___________________ TestCustomAssertMsg.test_single_line ___________________ self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> def test_single_line(self): - class A(object): + class A: a = 1 b = 2 @@ -607,13 +619,13 @@ Here is a nice run of several failures and how ``pytest`` presents things: E assert 1 == 2 E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a - failure_demo.py:262: AssertionError + failure_demo.py:261: AssertionError ____________________ TestCustomAssertMsg.test_multiline ____________________ self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> def test_multiline(self): - class A(object): + class A: a = 1 b = 2 @@ -626,13 +638,13 @@ Here is a nice run of several failures and how ``pytest`` presents things: E assert 1 == 2 E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a - failure_demo.py:269: AssertionError + failure_demo.py:268: AssertionError ___________________ TestCustomAssertMsg.test_custom_repr ___________________ self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> def test_custom_repr(self): - class JSON(object): + class JSON: a = 1 def __repr__(self): @@ -648,5 +660,50 @@ Here is a nice run of several failures and how ``pytest`` presents things: E assert 1 == 2 E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a - failure_demo.py:282: AssertionError - ======================== 44 failed in 0.12 seconds ========================= + failure_demo.py:281: AssertionError + ========================= short test summary info ========================== + FAILED failure_demo.py::test_generative[3-6] - assert (3 * 2) < 6 + FAILED failure_demo.py::TestFailing::test_simple - assert 42 == 43 + FAILED failure_demo.py::TestFailing::test_simple_multiline - assert 42 == 54 + FAILED failure_demo.py::TestFailing::test_not - assert not 42 + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_text - Asser... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_similar_text + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_multiline_text + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_long_text - ... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_long_text_multiline + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list - asser... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list_long - ... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dict - Asser... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - Assert... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list + FAILED failure_demo.py::TestSpecialisedExplanations::test_in_list - asser... + FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline + FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single + FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long + FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long_term + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dataclass - ... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_attrs - Asse... + FAILED failure_demo.py::test_attribute - assert 1 == 2 + FAILED failure_demo.py::test_attribute_instance - AssertionError: assert ... + FAILED failure_demo.py::test_attribute_failure - Exception: Failed to get... + FAILED failure_demo.py::test_attribute_multiple - AssertionError: assert ... + FAILED failure_demo.py::TestRaises::test_raises - ValueError: invalid lit... + FAILED failure_demo.py::TestRaises::test_raises_doesnt - Failed: DID NOT ... + FAILED failure_demo.py::TestRaises::test_raise - ValueError: demo error + FAILED failure_demo.py::TestRaises::test_tupleerror - ValueError: not eno... + FAILED failure_demo.py::TestRaises::test_reinterpret_fails_with_print_for_the_fun_of_it + FAILED failure_demo.py::TestRaises::test_some_error - NameError: name 'na... + FAILED failure_demo.py::test_dynamic_compile_shows_nicely - AssertionError + FAILED failure_demo.py::TestMoreErrors::test_complex_error - assert 44 == 43 + FAILED failure_demo.py::TestMoreErrors::test_z1_unpack_error - ValueError... + FAILED failure_demo.py::TestMoreErrors::test_z2_type_error - TypeError: c... + FAILED failure_demo.py::TestMoreErrors::test_startswith - AssertionError:... + FAILED failure_demo.py::TestMoreErrors::test_startswith_nested - Assertio... + FAILED failure_demo.py::TestMoreErrors::test_global_func - assert False + FAILED failure_demo.py::TestMoreErrors::test_instance - assert 42 != 42 + FAILED failure_demo.py::TestMoreErrors::test_compare - assert 11 < 5 + FAILED failure_demo.py::TestMoreErrors::test_try_finally - assert 1 == 0 + FAILED failure_demo.py::TestCustomAssertMsg::test_single_line - Assertion... + FAILED failure_demo.py::TestCustomAssertMsg::test_multiline - AssertionEr... + FAILED failure_demo.py::TestCustomAssertMsg::test_custom_repr - Assertion... + ============================ 44 failed in 0.12s ============================ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst index 1dee981be77..e9952dad4d7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/simple.rst @@ -3,6 +3,50 @@ Basic patterns and examples ========================================================== +How to change command line options defaults +------------------------------------------- + +It can be tedious to type the same series of command line options +every time you use ``pytest``. For example, if you always want to see +detailed info on skipped and xfailed tests, as well as have terser "dot" +progress output, you can write it into a configuration file: + +.. code-block:: ini + + # content of pytest.ini + [pytest] + addopts = -ra -q + + +Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command +line options while the environment is in use: + +.. code-block:: bash + + export PYTEST_ADDOPTS="-v" + +Here's how the command-line is built in the presence of ``addopts`` or the environment variable: + +.. code-block:: text + + <pytest.ini:addopts> $PYTEST_ADDOPTS <extra command-line arguments> + +So if the user executes in the command-line: + +.. code-block:: bash + + pytest -m slow + +The actual command line executed is: + +.. code-block:: bash + + pytest -ra -q -v -m slow + +Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example +above will show verbose output because ``-v`` overwrites ``-q``. + + .. _request example: Pass different values to a test function, depending on command line options @@ -65,7 +109,9 @@ Let's run this without supplying our new option: test_sample.py:6: AssertionError --------------------------- Captured stdout call --------------------------- first - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_sample.py::test_answer - assert 0 + 1 failed in 0.12s And now with supplying a command line option: @@ -89,7 +135,9 @@ And now with supplying a command line option: test_sample.py:6: AssertionError --------------------------- Captured stdout call --------------------------- second - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_sample.py::test_answer - assert 0 + 1 failed in 0.12s You can see that the command line option arrived in our test. This completes the basic pattern. However, one often rather wants to process @@ -127,12 +175,12 @@ directory with the above conftest.py: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 0 items - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== .. _`excontrolskip`: @@ -157,6 +205,10 @@ line option to control skipping of ``pytest.mark.slow`` marked tests: ) + def pytest_configure(config): + config.addinivalue_line("markers", "slow: mark test as slow to run") + + def pytest_collection_modifyitems(config, items): if config.getoption("--runslow"): # --runslow given in cli: do not skip slow tests @@ -188,7 +240,7 @@ and when running it will see a skipped "slow" test: $ pytest -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items @@ -197,7 +249,7 @@ and when running it will see a skipped "slow" test: ========================= short test summary info ========================== SKIPPED [1] test_module.py:8: need --runslow option to run - =================== 1 passed, 1 skipped in 0.12 seconds ==================== + ======================= 1 passed, 1 skipped in 0.12s ======================= Or run it including the ``slow`` marked test: @@ -205,17 +257,19 @@ Or run it including the ``slow`` marked test: $ pytest --runslow =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items test_module.py .. [100%] - ========================= 2 passed in 0.12 seconds ========================= + ============================ 2 passed in 0.12s ============================= + +.. _`__tracebackhide__`: Writing well integrated assertion helpers --------------------------------------------------- +----------------------------------------- .. regendoc:wipe @@ -234,7 +288,7 @@ Example: def checkconfig(x): __tracebackhide__ = True if not hasattr(x, "config"): - pytest.fail("not configured: %s" % (x,)) + pytest.fail("not configured: {}".format(x)) def test_something(): @@ -257,7 +311,9 @@ Let's run our little function: E Failed: not configured: 42 test_checkconfig.py:11: Failed - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_checkconfig.py::test_something - Failed: not configured: 42 + 1 failed in 0.12s If you only want to hide certain exceptions, you can set ``__tracebackhide__`` to a callable which gets the ``ExceptionInfo`` object. You can for example use @@ -276,7 +332,7 @@ this to make sure unexpected exception types aren't hidden: def checkconfig(x): __tracebackhide__ = operator.methodcaller("errisinstance", ConfigException) if not hasattr(x, "config"): - raise ConfigException("not configured: %s" % (x,)) + raise ConfigException("not configured: {}".format(x)) def test_something(): @@ -298,34 +354,31 @@ running from a test you can do something like this: .. code-block:: python - # content of conftest.py + # content of your_module.py - def pytest_configure(config): - import sys + _called_from_test = False - sys._called_from_test = True +.. code-block:: python + # content of conftest.py - def pytest_unconfigure(config): - import sys - del sys._called_from_test + def pytest_configure(config): + your_module._called_from_test = True -and then check for the ``sys._called_from_test`` flag: +and then check for the ``your_module._called_from_test`` flag: .. code-block:: python - if hasattr(sys, "_called_from_test"): + if your_module._called_from_test: # called from within a test run ... else: # called "normally" ... -accordingly in your application. It's also a good idea -to use your own application module rather than ``sys`` -for handling flag. +accordingly in your application. Adding info to test report header -------------------------------------------------------------- @@ -348,13 +401,13 @@ which will add the string to the test header accordingly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache project deps: mylib-1.1 rootdir: $REGENDOC_TMPDIR collected 0 items - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== .. regendoc:wipe @@ -377,14 +430,14 @@ which will add info only when run with "--v": $ pytest -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache info1: did you know that ... did you? rootdir: $REGENDOC_TMPDIR collecting ... collected 0 items - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== and nothing when run plainly: @@ -392,12 +445,12 @@ and nothing when run plainly: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 0 items - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== profiling test duration -------------------------- @@ -432,18 +485,18 @@ Now we can profile which test functions execute the slowest: $ pytest --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 3 items test_some_are_slow.py ... [100%] - ========================= slowest 3 test durations ========================= - 0.31s call test_some_are_slow.py::test_funcslow2 + =========================== slowest 3 durations ============================ + 0.30s call test_some_are_slow.py::test_funcslow2 0.20s call test_some_are_slow.py::test_funcslow1 0.10s call test_some_are_slow.py::test_funcfast - ========================= 3 passed in 0.12 seconds ========================= + ============================ 3 passed in 0.12s ============================= incremental testing - test steps --------------------------------------------------- @@ -460,21 +513,52 @@ an ``incremental`` marker which is to be used on classes: # content of conftest.py + from typing import Dict, Tuple import pytest + # store history of failures per test class name and per index in parametrize (if parametrize used) + _test_failed_incremental: Dict[str, Dict[Tuple[int, ...], str]] = {} + def pytest_runtest_makereport(item, call): if "incremental" in item.keywords: + # incremental marker is used if call.excinfo is not None: - parent = item.parent - parent._previousfailed = item + # the test has failed + # retrieve the class name of the test + cls_name = str(item.cls) + # retrieve the index of the test (if parametrize is used in combination with incremental) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + # retrieve the name of the test function + test_name = item.originalname or item.name + # store in _test_failed_incremental the original name of the failed test + _test_failed_incremental.setdefault(cls_name, {}).setdefault( + parametrize_index, test_name + ) def pytest_runtest_setup(item): if "incremental" in item.keywords: - previousfailed = getattr(item.parent, "_previousfailed", None) - if previousfailed is not None: - pytest.xfail("previous test failed (%s)" % previousfailed.name) + # retrieve the class name of the test + cls_name = str(item.cls) + # check if a previous test has failed for this class + if cls_name in _test_failed_incremental: + # retrieve the index of the test (if parametrize is used in combination with incremental) + parametrize_index = ( + tuple(item.callspec.indices.values()) + if hasattr(item, "callspec") + else () + ) + # retrieve the name of the first test function to fail for this class name and index + test_name = _test_failed_incremental[cls_name].get(parametrize_index, None) + # if name found, test has failed for the combination of class name & test name + if test_name is not None: + pytest.xfail("previous test failed ({})".format(test_name)) + These two hook implementations work together to abort incremental-marked tests in a class. Here is a test module example: @@ -487,7 +571,7 @@ tests in a class. Here is a test module example: @pytest.mark.incremental - class TestUserHandling(object): + class TestUserHandling: def test_login(self): pass @@ -507,7 +591,7 @@ If we run this: $ pytest -rx =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 4 items @@ -527,7 +611,7 @@ If we run this: ========================= short test summary info ========================== XFAIL test_step.py::TestUserHandling::test_deletion reason: previous test failed (test_modification) - ============== 1 failed, 2 passed, 1 xfailed in 0.12 seconds =============== + ================== 1 failed, 2 passed, 1 xfailed in 0.12s ================== We'll see that ``test_deletion`` was not executed because ``test_modification`` failed. It is reported as an "expected failure". @@ -552,7 +636,7 @@ Here is an example for making a ``db`` fixture available in a directory: import pytest - class DB(object): + class DB: pass @@ -591,7 +675,7 @@ We can run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 7 items @@ -640,7 +724,12 @@ We can run this: E assert 0 a/test_db2.py:2: AssertionError - ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.12 seconds ========== + ========================= short test summary info ========================== + FAILED test_step.py::TestUserHandling::test_modification - assert 0 + FAILED a/test_db.py::test_a1 - AssertionError: <conftest.DB object at 0x7... + FAILED a/test_db2.py::test_a2 - AssertionError: <conftest.DB object at 0x... + ERROR b/test_error.py::test_root + ============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12s ============== The two test modules in the ``a`` directory see the same ``db`` fixture instance while the one test in the sister-directory ``b`` doesn't see it. We could of course @@ -680,7 +769,7 @@ case we just write some information out to a ``failures`` file: with open("failures", mode) as f: # let's also access a fixture for the fun of it if "tmpdir" in item.fixturenames: - extra = " (%s)" % item.funcargs["tmpdir"] + extra = " ({})".format(item.funcargs["tmpdir"]) else: extra = "" @@ -705,7 +794,7 @@ and run them: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items @@ -729,7 +818,10 @@ and run them: E assert 0 test_module.py:6: AssertionError - ========================= 2 failed in 0.12 seconds ========================= + ========================= short test summary info ========================== + FAILED test_module.py::test_fail1 - assert 0 + FAILED test_module.py::test_fail2 - assert 0 + ============================ 2 failed in 0.12s ============================= you will have a "failures" file which contains the failing test ids: @@ -809,7 +901,7 @@ and run it: $ pytest -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 3 items @@ -844,7 +936,11 @@ and run it: E assert 0 test_module.py:19: AssertionError - ==================== 2 failed, 1 error in 0.12 seconds ===================== + ========================= short test summary info ========================== + FAILED test_module.py::test_call_fails - assert 0 + FAILED test_module.py::test_fail2 - assert 0 + ERROR test_module.py::test_setup_fails - assert 0 + ======================== 2 failed, 1 error in 0.12s ======================== You'll see that the fixture finalizers could use the precise reporting information. @@ -858,9 +954,9 @@ information. Sometimes a test session might get stuck and there might be no easy way to figure out which test got stuck, for example if pytest was run in quiet mode (``-q``) or you don't have access to the console -output. This is particularly a problem if the problem helps only sporadically, the famous "flaky" kind of tests. +output. This is particularly a problem if the problem happens only sporadically, the famous "flaky" kind of tests. -``pytest`` sets a ``PYTEST_CURRENT_TEST`` environment variable when running tests, which can be inspected +``pytest`` sets the :envvar:`PYTEST_CURRENT_TEST` environment variable when running tests, which can be inspected by process monitoring utilities or libraries like `psutil <https://pypi.org/project/psutil/>`_ to discover which test got stuck if necessary: @@ -874,8 +970,8 @@ test got stuck if necessary: print(f'pytest process {pid} running: {environ["PYTEST_CURRENT_TEST"]}') During the test session pytest will set ``PYTEST_CURRENT_TEST`` to the current test -:ref:`nodeid <nodeids>` and the current stage, which can be ``setup``, ``call`` -and ``teardown``. +:ref:`nodeid <nodeids>` and the current stage, which can be ``setup``, ``call``, +or ``teardown``. For example, when running a single test function named ``test_foo`` from ``foo_module.py``, ``PYTEST_CURRENT_TEST`` will be set to: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/special.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/special.rst index 800cb8e9010..6886b6268a2 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/special.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/special.rst @@ -5,30 +5,36 @@ A session-scoped fixture effectively has access to all collected test items. Here is an example of a fixture function which walks all collected tests and looks if their test class defines a ``callme`` method and -calls it:: +calls it: + +.. code-block:: python # content of conftest.py import pytest + @pytest.fixture(scope="session", autouse=True) def callattr_ahead_of_alltests(request): print("callattr_ahead_of_alltests called") - seen = set([None]) + seen = {None} session = request.node for item in session.items: cls = item.getparent(pytest.Class) if cls not in seen: if hasattr(cls.obj, "callme"): - cls.obj.callme() + cls.obj.callme() seen.add(cls) test classes may now define a ``callme`` method which -will be called ahead of running any tests:: +will be called ahead of running any tests: + +.. code-block:: python # content of test_module.py - class TestHello(object): + + class TestHello: @classmethod def callme(cls): print("callme called!") @@ -39,16 +45,20 @@ will be called ahead of running any tests:: def test_method2(self): print("test_method1 called") - class TestOther(object): + + class TestOther: @classmethod def callme(cls): print("callme other called") + def test_other(self): print("test other") + # works with unittest as well ... import unittest + class SomeTest(unittest.TestCase): @classmethod def callme(self): @@ -71,4 +81,4 @@ If you run this without output capturing: .test other .test_unit1 method called . - 4 passed in 0.12 seconds + 4 passed in 0.12s diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py index 88384a1562a..01e6da1ad2e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/example/xfail_demo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest xfail = pytest.mark.xfail diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/existingtestsuite.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/existingtestsuite.rst index d304b30c9a6..1e3e192bf3f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/existingtestsuite.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/existingtestsuite.rst @@ -15,7 +15,9 @@ Running an existing test suite with pytest Say you want to contribute to an existing repository somewhere. After pulling the code into your development space using some flavor of version control and (optionally) setting up a virtualenv -you will want to run:: +you will want to run: + +.. code-block:: bash cd <repository> pip install -e . # Environment dependent alternatives include @@ -30,5 +32,3 @@ reinstall every time you want to run your tests, and is less brittle than mucking about with sys.path to point your tests at local code. Also consider using :ref:`tox <use tox>`. - -.. include:: links.inc diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/faq.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/faq.rst deleted file mode 100644 index 5b13818ea5e..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/faq.rst +++ /dev/null @@ -1,156 +0,0 @@ -Some Issues and Questions -================================== - -.. note:: - - This FAQ is here only mostly for historic reasons. Checkout - `pytest Q&A at Stackoverflow <http://stackoverflow.com/search?q=pytest>`_ - for many questions and answers related to pytest and/or use - :ref:`contact channels` to get help. - -On naming, nosetests, licensing and magic ------------------------------------------------- - -How does pytest relate to nose and unittest? -+++++++++++++++++++++++++++++++++++++++++++++++++ - -``pytest`` and nose_ share basic philosophy when it comes -to running and writing Python tests. In fact, you can run many tests -written for nose with ``pytest``. nose_ was originally created -as a clone of ``pytest`` when ``pytest`` was in the ``0.8`` release -cycle. Note that starting with pytest-2.0 support for running unittest -test suites is majorly improved. - -how does pytest relate to twisted's trial? -++++++++++++++++++++++++++++++++++++++++++++++ - -Since some time ``pytest`` has builtin support for supporting tests -written using trial. It does not itself start a reactor, however, -and does not handle Deferreds returned from a test in pytest style. -If you are using trial's unittest.TestCase chances are that you can -just run your tests even if you return Deferreds. In addition, -there also is a dedicated `pytest-twisted -<https://pypi.org/project/pytest-twisted/>`_ plugin which allows you to -return deferreds from pytest-style tests, allowing the use of -:ref:`fixtures` and other features. - -how does pytest work with Django? -++++++++++++++++++++++++++++++++++++++++++++++ - -In 2012, some work is going into the `pytest-django plugin <https://pypi.org/project/pytest-django/>`_. It substitutes the usage of Django's -``manage.py test`` and allows the use of all pytest features_ most of which -are not available from Django directly. - -.. _features: features.html - - -What's this "magic" with pytest? (historic notes) -++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Around 2007 (version ``0.8``) some people thought that ``pytest`` -was using too much "magic". It had been part of the `pylib`_ which -contains a lot of unrelated python library code. Around 2010 there -was a major cleanup refactoring, which removed unused or deprecated code -and resulted in the new ``pytest`` PyPI package which strictly contains -only test-related code. This release also brought a complete pluginification -such that the core is around 300 lines of code and everything else is -implemented in plugins. Thus ``pytest`` today is a small, universally runnable -and customizable testing framework for Python. Note, however, that -``pytest`` uses metaprogramming techniques and reading its source is -thus likely not something for Python beginners. - -A second "magic" issue was the assert statement debugging feature. -Nowadays, ``pytest`` explicitly rewrites assert statements in test modules -in order to provide more useful :ref:`assert feedback <assertfeedback>`. -This completely avoids previous issues of confusing assertion-reporting. -It also means, that you can use Python's ``-O`` optimization without losing -assertions in test modules. - -You can also turn off all assertion interaction using the -``--assert=plain`` option. - -.. _`py namespaces`: index.html -.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py - - -Why can I use both ``pytest`` and ``py.test`` commands? -+++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -pytest used to be part of the py package, which provided several developer -utilities, all starting with ``py.<TAB>``, thus providing nice TAB-completion. -If you install ``pip install pycmd`` you get these tools from a separate -package. Once ``pytest`` became a separate package, the ``py.test`` name was -retained due to avoid a naming conflict with another tool. This conflict was -eventually resolved, and the ``pytest`` command was therefore introduced. In -future versions of pytest, we may deprecate and later remove the ``py.test`` -command to avoid perpetuating the confusion. - -pytest fixtures, parametrized tests -------------------------------------------------------- - -.. _funcargs: funcargs.html - -Is using pytest fixtures versus xUnit setup a style question? -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -For simple applications and for people experienced with nose_ or -unittest-style test setup using `xUnit style setup`_ probably -feels natural. For larger test suites, parametrized testing -or setup of complex test resources using fixtures_ may feel more natural. -Moreover, fixtures are ideal for writing advanced test support -code (like e.g. the monkeypatch_, the tmpdir_ or capture_ fixtures) -because the support code can register setup/teardown functions -in a managed class/module/function scope. - -.. _monkeypatch: monkeypatch.html -.. _tmpdir: tmpdir.html -.. _capture: capture.html -.. _fixtures: fixture.html - -.. _`why pytest_pyfuncarg__ methods?`: - -.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration - -Can I yield multiple values from a fixture function? -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -There are two conceptual reasons why yielding from a factory function -is not possible: - -* If multiple factories yielded values there would - be no natural place to determine the combination - policy - in real-world examples some combinations - often should not run. - -* Calling factories for obtaining test function arguments - is part of setting up and running a test. At that - point it is not possible to add new test calls to - the test collection anymore. - -However, with pytest-2.3 you can use the :ref:`@pytest.fixture` decorator -and specify ``params`` so that all tests depending on the factory-created -resource will run multiple times with different parameters. - -You can also use the ``pytest_generate_tests`` hook to -implement the `parametrization scheme of your choice`_. See also -:ref:`paramexamples` for more examples. - -.. _`parametrization scheme of your choice`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/ - -pytest interaction with other packages ---------------------------------------------------- - -Issues with pytest, multiprocess and setuptools? -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -On Windows the multiprocess package will instantiate sub processes -by pickling and thus implicitly re-import a lot of local modules. -Unfortunately, setuptools-0.6.11 does not ``if __name__=='__main__'`` -protect its generated command line script. This leads to infinite -recursion when running a test that instantiates Processes. - -As of mid-2013, there shouldn't be a problem anymore when you -use the standard setuptools (note that distribute has been merged -back into setuptools which is now shipped directly with virtualenv). - -.. include:: links.inc diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/fixture.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/fixture.rst index ed9c11b2ea7..90e88d87620 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/fixture.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/fixture.rst @@ -9,14 +9,20 @@ pytest fixtures: explicit, modular, scalable -.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit -.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software -.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection +.. _`xUnit`: https://en.wikipedia.org/wiki/XUnit +.. _`Software test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software +.. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection -The `purpose of test fixtures`_ is to provide a fixed baseline -upon which tests can reliably and repeatedly execute. pytest fixtures -offer dramatic improvements over the classic xUnit style of setup/teardown -functions: +`Software test fixtures`_ initialize test functions. They provide a +fixed baseline so that tests execute reliably and produce consistent, +repeatable, results. Initialization may setup services, state, or +other operating environments. These are accessed by test functions +through arguments; for each fixture used by a test function there is +typically a parameter (named after the fixture) in the test function's +definition. + +pytest fixtures offer dramatic improvements over the classic xUnit +style of setup/teardown functions: * fixtures have explicit names and are activated by declaring their use from test functions, modules, classes or whole projects. @@ -34,6 +40,74 @@ both styles, moving incrementally from classic to new style, as you prefer. You can also start out from existing :ref:`unittest.TestCase style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects. +:ref:`Fixtures <fixtures-api>` are defined using the +:ref:`@pytest.fixture <pytest.fixture-api>` decorator, :ref:`described +below <funcargs>`. Pytest has useful built-in fixtures, listed here +for reference: + + :fixture:`capfd` + Capture, as text, output to file descriptors ``1`` and ``2``. + + :fixture:`capfdbinary` + Capture, as bytes, output to file descriptors ``1`` and ``2``. + + :fixture:`caplog` + Control logging and access log entries. + + :fixture:`capsys` + Capture, as text, output to ``sys.stdout`` and ``sys.stderr``. + + :fixture:`capsysbinary` + Capture, as bytes, output to ``sys.stdout`` and ``sys.stderr``. + + :fixture:`cache` + Store and retrieve values across pytest runs. + + :fixture:`doctest_namespace` + Provide a dict injected into the docstests namespace. + + :fixture:`monkeypatch` + Temporarily modify classes, functions, dictionaries, + ``os.environ``, and other objects. + + :fixture:`pytestconfig` + Access to configuration values, pluginmanager and plugin hooks. + + :fixture:`record_property` + Add extra properties to the test. + + :fixture:`record_testsuite_property` + Add extra properties to the test suite. + + :fixture:`recwarn` + Record warnings emitted by test functions. + + :fixture:`request` + Provide information on the executing test function. + + :fixture:`testdir` + Provide a temporary test directory to aid in running, and + testing, pytest plugins. + + :fixture:`tmp_path` + Provide a :class:`pathlib.Path` object to a temporary directory + which is unique to each test function. + + :fixture:`tmp_path_factory` + Make session-scoped temporary directories and return + :class:`pathlib.Path` objects. + + :fixture:`tmpdir` + Provide a :class:`py.path.local` object to a temporary + directory which is unique to each test function; + replaced by :fixture:`tmp_path`. + + .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html + + :fixture:`tmpdir_factory` + Make session-scoped temporary directories and return + :class:`py.path.local` objects; + replaced by :fixture:`tmp_path_factory`. .. _`funcargs`: .. _`funcarg mechanism`: @@ -47,32 +121,37 @@ Fixtures as Function arguments Test functions can receive fixture objects by naming them as an input argument. For each argument name, a fixture function with that name provides the fixture object. Fixture functions are registered by marking them with -:py:func:`@pytest.fixture <_pytest.python.fixture>`. Let's look at a simple +:py:func:`@pytest.fixture <pytest.fixture>`. Let's look at a simple self-contained test module containing a fixture and a test function -using it:: +using it: + +.. code-block:: python # content of ./test_smtpsimple.py import pytest + @pytest.fixture def smtp_connection(): import smtplib + return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) + def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 - assert 0 # for demo purposes + assert 0 # for demo purposes Here, the ``test_ehlo`` needs the ``smtp_connection`` fixture value. pytest -will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>` +will discover and call the :py:func:`@pytest.fixture <pytest.fixture>` marked ``smtp_connection`` fixture function. Running the test looks like this: .. code-block:: pytest $ pytest test_smtpsimple.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -87,18 +166,20 @@ marked ``smtp_connection`` fixture function. Running the test looks like this: def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 - > assert 0 # for demo purposes + > assert 0 # for demo purposes E assert 0 - test_smtpsimple.py:11: AssertionError - ========================= 1 failed in 0.12 seconds ========================= + test_smtpsimple.py:14: AssertionError + ========================= short test summary info ========================== + FAILED test_smtpsimple.py::test_ehlo - assert 0 + ============================ 1 failed in 0.12s ============================= In the failure traceback we see that the test function was called with a ``smtp_connection`` argument, the ``smtplib.SMTP()`` instance created by the fixture function. The test function fails on our deliberate ``assert 0``. Here is the exact protocol used by ``pytest`` to call the test function this way: -1. pytest :ref:`finds <test discovery>` the ``test_ehlo`` because +1. pytest :ref:`finds <test discovery>` the test ``test_ehlo`` because of the ``test_`` prefix. The test function needs a function argument named ``smtp_connection``. A matching fixture function is discovered by looking for a fixture-marked function named ``smtp_connection``. @@ -163,15 +244,15 @@ and `pytest-datafiles <https://pypi.org/project/pytest-datafiles/>`__. .. _smtpshared: -Scope: sharing a fixture instance across tests in a class, module or session ----------------------------------------------------------------------------- +Scope: sharing fixtures across classes, modules, packages or session +-------------------------------------------------------------------- .. regendoc:wipe Fixtures requiring network access depend on connectivity and are usually time-expensive to create. Extending the previous example, we can add a ``scope="module"`` parameter to the -:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation +:py:func:`@pytest.fixture <pytest.fixture>` invocation to cause the decorated ``smtp_connection`` fixture function to only be invoked once per test *module* (the default is to invoke once per test *function*). Multiple test functions in a test module will thus @@ -180,12 +261,15 @@ Possible values for ``scope`` are: ``function``, ``class``, ``module``, ``packag The next example puts the fixture function into a separate ``conftest.py`` file so that tests from multiple test modules in the directory can -access the fixture function:: +access the fixture function: + +.. code-block:: python # content of conftest.py import pytest import smtplib + @pytest.fixture(scope="module") def smtp_connection(): return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) @@ -193,16 +277,20 @@ access the fixture function:: The name of the fixture again is ``smtp_connection`` and you can access its result by listing the name ``smtp_connection`` as an input parameter in any test or fixture function (in or below the directory where ``conftest.py`` is -located):: +located): + +.. code-block:: python # content of test_module.py + def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 assert b"smtp.gmail.com" in msg assert 0 # for demo purposes + def test_noop(smtp_connection): response, msg = smtp_connection.noop() assert response == 250 @@ -215,7 +303,7 @@ inspect what is going on and can now run the tests: $ pytest test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items @@ -234,7 +322,7 @@ inspect what is going on and can now run the tests: > assert 0 # for demo purposes E assert 0 - test_module.py:6: AssertionError + test_module.py:7: AssertionError ________________________________ test_noop _________________________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef> @@ -245,8 +333,11 @@ inspect what is going on and can now run the tests: > assert 0 # for demo purposes E assert 0 - test_module.py:11: AssertionError - ========================= 2 failed in 0.12 seconds ========================= + test_module.py:13: AssertionError + ========================= short test summary info ========================== + FAILED test_module.py::test_ehlo - assert 0 + FAILED test_module.py::test_noop - assert 0 + ============================ 2 failed in 0.12s ============================= You see the two ``assert 0`` failing and more importantly you can also see that the same (module-scoped) ``smtp_connection`` object was passed into the @@ -265,75 +356,77 @@ instance, you can simply declare it: # all tests needing it ... -Finally, the ``class`` scope will invoke the fixture once per test *class*. - -.. note:: - - Pytest will only cache one instance of a fixture at a time. - This means that when using a parametrized fixture, pytest may invoke a fixture more than once in the given scope. - - -``package`` scope (experimental) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Fixture scopes +^^^^^^^^^^^^^^ +Fixtures are created when first requested by a test, and are destroyed based on their ``scope``: -In pytest 3.7 the ``package`` scope has been introduced. Package-scoped fixtures -are finalized when the last test of a *package* finishes. +* ``function``: the default scope, the fixture is destroyed at the end of the test. +* ``class``: the fixture is destroyed during teardown of the last test in the class. +* ``module``: the fixture is destroyed during teardown of the last test in the module. +* ``package``: the fixture is destroyed during teardown of the last test in the package. +* ``session``: the fixture is destroyed at the end of the test session. -.. warning:: - This functionality is considered **experimental** and may be removed in future - versions if hidden corner-cases or serious problems with this functionality - are discovered after it gets more usage in the wild. +.. note:: - Use this new feature sparingly and please make sure to report any issues you find. + Pytest only caches one instance of a fixture at a time, which + means that when using a parametrized fixture, pytest may invoke a fixture more than once in + the given scope. +.. _dynamic scope: -Higher-scoped fixtures are instantiated first ---------------------------------------------- +Dynamic scope +^^^^^^^^^^^^^ +.. versionadded:: 5.2 +In some cases, you might want to change the scope of the fixture without changing the code. +To do that, pass a callable to ``scope``. The callable must return a string with a valid scope +and will be executed only once - during the fixture definition. It will be called with two +keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object. -Within a function request for features, fixture of higher-scopes (such as ``session``) are instantiated first than -lower-scoped fixtures (such as ``function`` or ``class``). The relative order of fixtures of same scope follows -the declared order in the test function and honours dependencies between fixtures. - -Consider the code below: +This can be especially useful when dealing with fixtures that need time for setup, like spawning +a docker container. You can use the command-line argument to control the scope of the spawned +containers for different environments. See the example below. .. code-block:: python - @pytest.fixture(scope="session") - def s1(): - pass + def determine_scope(fixture_name, config): + if config.getoption("--keep-containers", None): + return "session" + return "function" - @pytest.fixture(scope="module") - def m1(): - pass + @pytest.fixture(scope=determine_scope) + def docker_container(): + yield spawn_container() - @pytest.fixture - def f1(tmpdir): - pass +Order: Higher-scoped fixtures are instantiated first +---------------------------------------------------- - @pytest.fixture - def f2(): - pass - def test_foo(f1, m1, f2, s1): - ... +Within a function request for fixtures, those of higher-scopes (such as ``session``) are instantiated before +lower-scoped fixtures (such as ``function`` or ``class``). The relative order of fixtures of same scope follows +the declared order in the test function and honours dependencies between fixtures. Autouse fixtures will be +instantiated before explicitly used fixtures. + +Consider the code below: +.. literalinclude:: example/fixtures/test_fixtures_order.py -The fixtures requested by ``test_foo`` will be instantiated in the following order: +The fixtures requested by ``test_order`` will be instantiated in the following order: 1. ``s1``: is the highest-scoped fixture (``session``). 2. ``m1``: is the second highest-scoped fixture (``module``). -3. ``tmpdir``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point - because it is a dependency of ``f1``. -4. ``f1``: is the first ``function``-scoped fixture in ``test_foo`` parameter list. -5. ``f2``: is the last ``function``-scoped fixture in ``test_foo`` parameter list. +3. ``a1``: is a ``function``-scoped ``autouse`` fixture: it will be instantiated before other fixtures + within the same scope. +4. ``f3``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point +5. ``f1``: is the first ``function``-scoped fixture in ``test_order`` parameter list. +6. ``f2``: is the last ``function``-scoped fixture in ``test_order`` parameter list. .. _`finalization`: @@ -371,7 +464,10 @@ Let's execute it: $ pytest -s -q --tb=no FFteardown smtp - 2 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_module.py::test_ehlo - assert 0 + FAILED test_module.py::test_noop - assert 0 + 2 failed in 0.12s We see that the ``smtp_connection`` instance is finalized after the two tests finished execution. Note that if we decorated our fixture @@ -400,6 +496,34 @@ The ``smtp_connection`` connection will be closed after the test finished execution because the ``smtp_connection`` object automatically closes when the ``with`` statement ends. +Using the contextlib.ExitStack context manager finalizers will always be called +regardless if the fixture *setup* code raises an exception. This is handy to properly +close all resources created by a fixture even if one of them fails to be created/acquired: + +.. code-block:: python + + # content of test_yield3.py + + import contextlib + + import pytest + + + @contextlib.contextmanager + def connect(port): + ... # create connection + yield + ... # close connection + + + @pytest.fixture + def equipments(): + with contextlib.ExitStack() as stack: + yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")] + +In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still +be properly closed. + Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the *teardown* code (after the ``yield``) will not be called. @@ -428,27 +552,39 @@ Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for clean return smtp_connection # provide the fixture value -Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test -ends, but ``addfinalizer`` has two key differences over ``yield``: +Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup: -1. It is possible to register multiple finalizer functions. +.. code-block:: python -2. Finalizers will always be called regardless if the fixture *setup* code raises an exception. - This is handy to properly close all resources created by a fixture even if one of them - fails to be created/acquired:: + # content of test_yield3.py - @pytest.fixture - def equipments(request): - r = [] - for port in ('C1', 'C3', 'C28'): - equip = connect(port) - request.addfinalizer(equip.disconnect) - r.append(equip) - return r + import contextlib + import functools + + import pytest + + + @contextlib.contextmanager + def connect(port): + ... # create connection + yield + ... # close connection - In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still - be properly closed. Of course, if an exception happens before the finalize function is - registered then it will not be executed. + + @pytest.fixture + def equipments(request): + r = [] + for port in ("C1", "C3", "C28"): + cm = connect(port) + equip = cm.__enter__() + request.addfinalizer(functools.partial(cm.__exit__, None, None, None)) + r.append(equip) + return r + + +Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test +ends. Of course, if an exception happens before the finalize function is registered then it +will not be executed. .. _`request-context`: @@ -456,21 +592,24 @@ ends, but ``addfinalizer`` has two key differences over ``yield``: Fixtures can introspect the requesting test context ------------------------------------------------------------- -Fixture functions can accept the :py:class:`request <FixtureRequest>` object +Fixture functions can accept the :py:class:`request <_pytest.fixtures.FixtureRequest>` object to introspect the "requesting" test function, class or module context. Further extending the previous ``smtp_connection`` fixture example, let's -read an optional server URL from the test module which uses our fixture:: +read an optional server URL from the test module which uses our fixture: + +.. code-block:: python # content of conftest.py import pytest import smtplib + @pytest.fixture(scope="module") def smtp_connection(request): server = getattr(request.module, "smtpserver", "smtp.gmail.com") smtp_connection = smtplib.SMTP(server, 587, timeout=5) yield smtp_connection - print("finalizing %s (%s)" % (smtp_connection, server)) + print("finalizing {} ({})".format(smtp_connection, server)) smtp_connection.close() We use the ``request.module`` attribute to optionally obtain an @@ -482,15 +621,21 @@ again, nothing much has changed: $ pytest -s -q --tb=no FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com) - 2 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_module.py::test_ehlo - assert 0 + FAILED test_module.py::test_noop - assert 0 + 2 failed in 0.12s Let's quickly create another test module that actually sets the -server URL in its module namespace:: +server URL in its module namespace: + +.. code-block:: python # content of test_anothersmtp.py smtpserver = "mail.python.org" # will be read by smtp fixture + def test_showhelo(smtp_connection): assert 0, smtp_connection.helo() @@ -502,16 +647,49 @@ Running it: F [100%] ================================= FAILURES ================================= ______________________________ test_showhelo _______________________________ - test_anothersmtp.py:5: in test_showhelo + test_anothersmtp.py:6: in test_showhelo assert 0, smtp_connection.helo() E AssertionError: (250, b'mail.python.org') E assert 0 ------------------------- Captured stdout teardown ------------------------- finalizing <smtplib.SMTP object at 0xdeadbeef> (mail.python.org) + ========================= short test summary info ========================== + FAILED test_anothersmtp.py::test_showhelo - AssertionError: (250, b'mail.... voila! The ``smtp_connection`` fixture function picked up our mail server name from the module namespace. +.. _`using-markers`: + +Using markers to pass data to fixtures +------------------------------------------------------------- + +Using the :py:class:`request <_pytest.fixtures.FixtureRequest>` object, a fixture can also access +markers which are applied to a test function. This can be useful to pass data +into a fixture from a test: + +.. code-block:: python + + import pytest + + + @pytest.fixture + def fixt(request): + marker = request.node.get_closest_marker("fixt_data") + if marker is None: + # Handle missing marker in some way... + data = None + else: + data = marker.args[0] + + # Do something with the data + return data + + + @pytest.mark.fixt_data(42) + def test_fixt(fixt): + assert fixt == 42 + .. _`fixture-factory`: Factories as fixtures @@ -522,16 +700,14 @@ of a fixture is needed multiple times in a single test. Instead of returning data directly, the fixture instead returns a function which generates the data. This function can then be called multiple times in the test. -Factories can have have parameters as needed:: +Factories can have parameters as needed: + +.. code-block:: python @pytest.fixture def make_customer_record(): - def _make_customer_record(name): - return { - "name": name, - "orders": [] - } + return {"name": name, "orders": []} return _make_customer_record @@ -541,7 +717,9 @@ Factories can have have parameters as needed:: customer_2 = make_customer_record("Mike") customer_3 = make_customer_record("Meredith") -If the data created by the factory requires managing, the fixture can take care of that:: +If the data created by the factory requires managing, the fixture can take care of that: + +.. code-block:: python @pytest.fixture def make_customer_record(): @@ -580,22 +758,24 @@ configured in multiple ways. Extending the previous example, we can flag the fixture to create two ``smtp_connection`` fixture instances which will cause all tests using the fixture to run twice. The fixture function gets access to each parameter -through the special :py:class:`request <FixtureRequest>` object:: +through the special :py:class:`request <FixtureRequest>` object: + +.. code-block:: python # content of conftest.py import pytest import smtplib - @pytest.fixture(scope="module", - params=["smtp.gmail.com", "mail.python.org"]) + + @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"]) def smtp_connection(request): smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) yield smtp_connection - print("finalizing %s" % smtp_connection) + print("finalizing {}".format(smtp_connection)) smtp_connection.close() The main change is the declaration of ``params`` with -:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values +:py:func:`@pytest.fixture <pytest.fixture>`, a list of values for each of which the fixture function will execute and can access a value via ``request.param``. No test function code needs to change. So let's just do another run: @@ -616,7 +796,7 @@ So let's just do another run: > assert 0 # for demo purposes E assert 0 - test_module.py:6: AssertionError + test_module.py:7: AssertionError ________________________ test_noop[smtp.gmail.com] _________________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef> @@ -627,7 +807,7 @@ So let's just do another run: > assert 0 # for demo purposes E assert 0 - test_module.py:11: AssertionError + test_module.py:13: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ smtp_connection = <smtplib.SMTP object at 0xdeadbeef> @@ -638,7 +818,7 @@ So let's just do another run: > assert b"smtp.gmail.com" in msg E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING' - test_module.py:5: AssertionError + test_module.py:6: AssertionError -------------------------- Captured stdout setup --------------------------- finalizing <smtplib.SMTP object at 0xdeadbeef> ________________________ test_noop[mail.python.org] ________________________ @@ -651,10 +831,15 @@ So let's just do another run: > assert 0 # for demo purposes E assert 0 - test_module.py:11: AssertionError + test_module.py:13: AssertionError ------------------------- Captured stdout teardown ------------------------- finalizing <smtplib.SMTP object at 0xdeadbeef> - 4 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_module.py::test_ehlo[smtp.gmail.com] - assert 0 + FAILED test_module.py::test_noop[smtp.gmail.com] - assert 0 + FAILED test_module.py::test_ehlo[mail.python.org] - AssertionError: asser... + FAILED test_module.py::test_noop[mail.python.org] - assert 0 + 4 failed in 0.12s We see that our two test functions each ran twice, against the different ``smtp_connection`` instances. Note also, that with the ``mail.python.org`` @@ -668,39 +853,46 @@ be used with ``-k`` to select specific cases to run, and they will also identify the specific case when one is failing. Running pytest with ``--collect-only`` will show the generated IDs. -Numbers, strings, booleans and None will have their usual string +Numbers, strings, booleans and ``None`` will have their usual string representation used in the test ID. For other objects, pytest will make a string based on the argument name. It is possible to customise the string used in a test ID for a certain fixture value by using the -``ids`` keyword argument:: +``ids`` keyword argument: + +.. code-block:: python # content of test_ids.py import pytest + @pytest.fixture(params=[0, 1], ids=["spam", "ham"]) def a(request): return request.param + def test_a(a): pass + def idfn(fixture_value): if fixture_value == 0: return "eggs" else: return None + @pytest.fixture(params=[0, 1], ids=idfn) def b(request): return request.param + def test_b(b): pass The above shows how ``ids`` can be either a list of strings to use or a function which will be called with the fixture value and then has to return a string to use. In the latter case if the function -return ``None`` then pytest's auto-generated ID will be used. +returns ``None`` then pytest's auto-generated ID will be used. Running the above tests results in the following test IDs being used: @@ -708,10 +900,11 @@ Running the above tests results in the following test IDs being used: $ pytest --collect-only =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 10 items + <Module test_anothersmtp.py> <Function test_showhelo[smtp.gmail.com]> <Function test_showhelo[mail.python.org]> @@ -726,7 +919,7 @@ Running the above tests results in the following test IDs being used: <Function test_ehlo[mail.python.org]> <Function test_noop[mail.python.org]> - ======================= no tests ran in 0.12 seconds ======================= + ========================== no tests ran in 0.12s =========================== .. _`fixture-parametrize-marks`: @@ -736,14 +929,19 @@ Using marks with parametrized fixtures :func:`pytest.param` can be used to apply marks in values sets of parametrized fixtures in the same way that they can be used with :ref:`@pytest.mark.parametrize <@pytest.mark.parametrize>`. -Example:: +Example: + +.. code-block:: python # content of test_fixture_marks.py import pytest + + @pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)]) def data_set(request): return request.param + def test_data(data_set): pass @@ -753,7 +951,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``: $ pytest test_fixture_marks.py -v =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 3 items @@ -762,32 +960,37 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``: test_fixture_marks.py::test_data[1] PASSED [ 66%] test_fixture_marks.py::test_data[2] SKIPPED [100%] - =================== 2 passed, 1 skipped in 0.12 seconds ==================== + ======================= 2 passed, 1 skipped in 0.12s ======================= .. _`interdependent fixtures`: Modularity: using fixtures from a fixture function ---------------------------------------------------------- -You can not only use fixtures in test functions but fixture functions +In addition to using fixtures in test functions, fixture functions can use other fixtures themselves. This contributes to a modular design of your fixtures and allows re-use of framework-specific fixtures across many projects. As a simple example, we can extend the previous example and instantiate an object ``app`` where we stick the already defined -``smtp_connection`` resource into it:: +``smtp_connection`` resource into it: + +.. code-block:: python # content of test_appsetup.py import pytest - class App(object): + + class App: def __init__(self, smtp_connection): self.smtp_connection = smtp_connection + @pytest.fixture(scope="module") def app(smtp_connection): return App(smtp_connection) + def test_smtp_connection_exists(app): assert app.smtp_connection @@ -798,7 +1001,7 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 2 items @@ -806,7 +1009,7 @@ Here we declare an ``app`` fixture which receives the previously defined test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%] test_appsetup.py::test_smtp_connection_exists[mail.python.org] PASSED [100%] - ========================= 2 passed in 0.12 seconds ========================= + ============================ 2 passed in 0.12s ============================= Due to the parametrization of ``smtp_connection``, the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -836,31 +1039,40 @@ this eases testing of applications which create and use global state. The following example uses two parametrized fixtures, one of which is scoped on a per-module basis, and all the functions perform ``print`` calls -to show the setup/teardown flow:: +to show the setup/teardown flow: + +.. code-block:: python # content of test_module.py import pytest + @pytest.fixture(scope="module", params=["mod1", "mod2"]) def modarg(request): param = request.param - print(" SETUP modarg %s" % param) + print(" SETUP modarg", param) yield param - print(" TEARDOWN modarg %s" % param) + print(" TEARDOWN modarg", param) - @pytest.fixture(scope="function", params=[1,2]) + + @pytest.fixture(scope="function", params=[1, 2]) def otherarg(request): param = request.param - print(" SETUP otherarg %s" % param) + print(" SETUP otherarg", param) yield param - print(" TEARDOWN otherarg %s" % param) + print(" TEARDOWN otherarg", param) + def test_0(otherarg): - print(" RUN test0 with otherarg %s" % otherarg) + print(" RUN test0 with otherarg", otherarg) + + def test_1(modarg): - print(" RUN test1 with modarg %s" % modarg) + print(" RUN test1 with modarg", modarg) + + def test_2(otherarg, modarg): - print(" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg)) + print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg)) Let's run the tests in verbose mode and with looking at the print-output: @@ -869,7 +1081,7 @@ Let's run the tests in verbose mode and with looking at the print-output: $ pytest -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collecting ... collected 8 items @@ -907,7 +1119,7 @@ Let's run the tests in verbose mode and with looking at the print-output: TEARDOWN modarg mod2 - ========================= 8 passed in 0.12 seconds ========================= + ============================ 8 passed in 0.12s ============================= You can see that the parametrized module-scoped ``modarg`` resource caused an ordering of test execution that lead to the fewest possible "active" resources. @@ -924,8 +1136,8 @@ and teared down after every test that used it. .. _`usefixtures`: -Using fixtures from classes, modules or projects ----------------------------------------------------------------------- +Use fixtures in classes and modules with ``usefixtures`` +-------------------------------------------------------- .. regendoc:wipe @@ -935,27 +1147,39 @@ current working directory but otherwise do not care for the concrete directory. Here is how you can use the standard `tempfile <http://docs.python.org/library/tempfile.html>`_ and pytest fixtures to achieve it. We separate the creation of the fixture into a conftest.py -file:: +file: + +.. code-block:: python # content of conftest.py - import pytest - import tempfile import os + import shutil + import tempfile - @pytest.fixture() + import pytest + + + @pytest.fixture def cleandir(): + old_cwd = os.getcwd() newpath = tempfile.mkdtemp() os.chdir(newpath) + yield + os.chdir(old_cwd) + shutil.rmtree(newpath) -and declare its use in a test module via a ``usefixtures`` marker:: +and declare its use in a test module via a ``usefixtures`` marker: + +.. code-block:: python # content of test_setenv.py import os import pytest + @pytest.mark.usefixtures("cleandir") - class TestDirectoryInit(object): + class TestDirectoryInit: def test_cwd_starts_empty(self): assert os.listdir(os.getcwd()) == [] with open("myfile", "w") as f: @@ -973,7 +1197,7 @@ to verify our fixture is activated and the tests pass: $ pytest -q .. [100%] - 2 passed in 0.12 seconds + 2 passed in 0.12s You can specify multiple fixtures like this: @@ -983,15 +1207,12 @@ You can specify multiple fixtures like this: def test(): ... -and you may specify fixture usage at the test module level, using -a generic feature of the mark mechanism: +and you may specify fixture usage at the test module level using :globalvar:`pytestmark`: .. code-block:: python pytestmark = pytest.mark.usefixtures("cleandir") -Note that the assigned variable *must* be called ``pytestmark``, assigning e.g. -``foomark`` will not activate the fixtures. It is also possible to put fixtures required by all tests in your project into an ini-file: @@ -1032,25 +1253,32 @@ without declaring a function argument explicitly or a `usefixtures`_ decorator. As a practical example, suppose we have a database fixture which has a begin/rollback/commit architecture and we want to automatically surround each test method by a transaction and a rollback. Here is a dummy -self-contained implementation of this idea:: +self-contained implementation of this idea: + +.. code-block:: python # content of test_db_transact.py import pytest - class DB(object): + + class DB: def __init__(self): self.intransaction = [] + def begin(self, name): self.intransaction.append(name) + def rollback(self): self.intransaction.pop() + @pytest.fixture(scope="module") def db(): return DB() - class TestClass(object): + + class TestClass: @pytest.fixture(autouse=True) def transact(self, request, db): db.begin(request.function.__name__) @@ -1074,7 +1302,7 @@ If we run it, we get two passing tests: $ pytest -q .. [100%] - 2 passed in 0.12 seconds + 2 passed in 0.12s Here is how autouse fixtures work in other scopes: @@ -1098,7 +1326,9 @@ Here is how autouse fixtures work in other scopes: Note that the above ``transact`` fixture may very well be a fixture that you want to make available in your project without having it generally active. The canonical way to do that is to put the transact definition -into a conftest.py file **without** using ``autouse``:: +into a conftest.py file **without** using ``autouse``: + +.. code-block:: python # content of conftest.py @pytest.fixture @@ -1107,10 +1337,12 @@ into a conftest.py file **without** using ``autouse``:: yield db.rollback() -and then e.g. have a TestClass using it by declaring the need:: +and then e.g. have a TestClass using it by declaring the need: + +.. code-block:: python @pytest.mark.usefixtures("transact") - class TestClass(object): + class TestClass: def test_method1(self): ... @@ -1299,3 +1531,37 @@ Given the tests file structure is: In the example above, a parametrized fixture is overridden with a non-parametrized version, and a non-parametrized fixture is overridden with a parametrized version for certain test module. The same applies for the test folder level obviously. + + +Using fixtures from other projects +---------------------------------- + +Usually projects that provide pytest support will use :ref:`entry points <setuptools entry points>`, +so just installing those projects into an environment will make those fixtures available for use. + +In case you want to use fixtures from a project that does not use entry points, you can +define :globalvar:`pytest_plugins` in your top ``conftest.py`` file to register that module +as a plugin. + +Suppose you have some fixtures in ``mylibrary.fixtures`` and you want to reuse them into your +``app/tests`` directory. + +All you need to do is to define :globalvar:`pytest_plugins` in ``app/tests/conftest.py`` +pointing to that module. + +.. code-block:: python + + pytest_plugins = "mylibrary.fixtures" + +This effectively registers ``mylibrary.fixtures`` as a plugin, making all its fixtures and +hooks available to tests in ``app/tests``. + +.. note:: + + Sometimes users will *import* fixtures from other projects for use, however this is not + recommended: importing fixtures into a module will register them in pytest + as *defined* in that module. + + This has minor consequences, such as appearing multiple times in ``pytest --help``, + but it is not **recommended** because this behavior might change/stop working + in future versions. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/flaky.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/flaky.rst index 8e340316ea0..b6fc1520c0b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/flaky.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/flaky.rst @@ -28,7 +28,7 @@ Flaky tests sometimes appear when a test suite is run in parallel (such as use o Overly strict assertion ~~~~~~~~~~~~~~~~~~~~~~~ -Overly strict assertions can cause problems with floating point comparison as well as timing issues. `pytest.approx <https://docs.pytest.org/en/latest/reference.html#pytest-approx>`_ is useful here. +Overly strict assertions can cause problems with floating point comparison as well as timing issues. `pytest.approx <https://docs.pytest.org/en/stable/reference.html#pytest-approx>`_ is useful here. Pytest features @@ -43,7 +43,8 @@ Xfail strict PYTEST_CURRENT_TEST ~~~~~~~~~~~~~~~~~~~ -:ref:`pytest current test env` may be useful for figuring out "which test got stuck". +:envvar:`PYTEST_CURRENT_TEST` may be useful for figuring out "which test got stuck". +See :ref:`pytest current test env` for more details. Plugins @@ -122,4 +123,4 @@ Resources * Google: * `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016 - * `Where do Google's flaky tests come from? <https://docs.google.com/document/d/1mZ0-Kc97DI_F3tf_GBW_NB_aqka-P1jVOsFfufxqUUM/edit#heading=h.ec0r4fypsleh>`_ by Jeff Listfield, 2017 + * `Where do Google's flaky tests come from? <https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html>`_ by Jeff Listfield, 2017 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst index 4fa9b79ae7a..0c4913edff8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/funcarg_compare.rst @@ -7,7 +7,7 @@ pytest-2.3: reasoning for fixture/funcarg evolution **Target audience**: Reading this document requires basic knowledge of python testing, xUnit setup methods and the (previous) basic pytest -funcarg mechanism, see https://docs.pytest.org/en/latest/historical-notes.html#funcargs-and-pytest-funcarg. +funcarg mechanism, see https://docs.pytest.org/en/stable/historical-notes.html#funcargs-and-pytest-funcarg. If you are new to pytest, then you can simply ignore this section and read the other sections. @@ -21,19 +21,23 @@ funcarg for a test function is required. If a factory wants to re-use a resource across different scopes, it often used the ``request.cached_setup()`` helper to manage caching of resources. Here is a basic example how we could implement -a per-session Database object:: +a per-session Database object: + +.. code-block:: python # content of conftest.py - class Database(object): + class Database: def __init__(self): print("database instance created") + def destroy(self): print("database instance destroyed") + def pytest_funcarg__db(request): - return request.cached_setup(setup=DataBase, - teardown=lambda db: db.destroy, - scope="session") + return request.cached_setup( + setup=DataBase, teardown=lambda db: db.destroy, scope="session" + ) There are several limitations and difficulties with this approach: @@ -47,7 +51,7 @@ There are several limitations and difficulties with this approach: performs parametrization at the places where the resource is used. Moreover, you need to modify the factory to use an ``extrakey`` parameter containing ``request.param`` to the - :py:func:`~python.Request.cached_setup` call. + ``Request.cached_setup`` call. 3. Multiple parametrized session-scoped resources will be active at the same time, making it hard for them to affect global state @@ -68,7 +72,9 @@ Direct scoping of fixture/funcarg factories Instead of calling cached_setup() with a cache scope, you can use the :ref:`@pytest.fixture <pytest.fixture>` decorator and directly state -the scope:: +the scope: + +.. code-block:: python @pytest.fixture(scope="session") def db(request): @@ -90,11 +96,13 @@ Previously, funcarg factories could not directly cause parametrization. You needed to specify a ``@parametrize`` decorator on your test function or implement a ``pytest_generate_tests`` hook to perform parametrization, i.e. calling a test multiple times with different value -sets. pytest-2.3 introduces a decorator for use on the factory itself:: +sets. pytest-2.3 introduces a decorator for use on the factory itself: + +.. code-block:: python @pytest.fixture(params=["mysql", "pg"]) def db(request): - ... # use request.param + ... # use request.param Here the factory will be invoked twice (with the respective "mysql" and "pg" values set as ``request.param`` attributes) and all of @@ -105,9 +113,11 @@ This new way of parametrizing funcarg factories should in many cases allow to re-use already written factories because effectively ``request.param`` was already used when test functions/classes were parametrized via -:py:func:`~_pytest.python.Metafunc.parametrize(indirect=True)` calls. +:py:func:`metafunc.parametrize(indirect=True) <_pytest.python.Metafunc.parametrize>` calls. + +Of course it's perfectly fine to combine parametrization and scoping: -Of course it's perfectly fine to combine parametrization and scoping:: +.. code-block:: python @pytest.fixture(scope="session", params=["mysql", "pg"]) def db(request): @@ -128,7 +138,9 @@ No ``pytest_funcarg__`` prefix when using @fixture decorator When using the ``@fixture`` decorator the name of the function denotes the name under which the resource can be accessed as a function -argument:: +argument: + +.. code-block:: python @pytest.fixture() def db(request): @@ -137,7 +149,9 @@ argument:: The name under which the funcarg resource can be requested is ``db``. You can still use the "old" non-decorator way of specifying funcarg factories -aka:: +aka: + +.. code-block:: python def pytest_funcarg__db(request): ... @@ -156,7 +170,7 @@ several problems: 1. in distributed testing the master process would setup test resources that are never needed because it only co-ordinates the test run - activities of the slave processes. + activities of the worker processes. 2. if you only perform a collection (with "--collect-only") resource-setup will still be executed. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst index 882385b2960..5a5f0fa7a43 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/getting-started.rst @@ -1,9 +1,9 @@ Installation and Getting Started =================================== -**Pythons**: Python 2.7, 3.4, 3.5, 3.6, 3.7, Jython, PyPy-2.3 +**Pythons**: Python 3.5, 3.6, 3.7, 3.8, 3.9, PyPy3 -**Platforms**: Unix/Posix and Windows +**Platforms**: Linux and Windows **PyPI package name**: `pytest <https://pypi.org/project/pytest/>`_ @@ -28,19 +28,22 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - This is pytest version 4.x.y, imported from $PYTHON_PREFIX/lib/python3.7/site-packages/pytest.py + pytest 6.1.1 .. _`simpletest`: Create your first test ---------------------------------------------------------- -Create a simple test function with just four lines of code:: +Create a simple test function with just four lines of code: + +.. code-block:: python # content of test_sample.py def func(x): return x + 1 + def test_answer(): assert func(3) == 5 @@ -50,7 +53,7 @@ That’s it. You can now execute the test function: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -65,10 +68,12 @@ That’s it. You can now execute the test function: E assert 4 == 5 E + where 4 = func(3) - test_sample.py:5: AssertionError - ========================= 1 failed in 0.12 seconds ========================= + test_sample.py:6: AssertionError + ========================= short test summary info ========================== + FAILED test_sample.py::test_answer - assert 4 == 5 + ============================ 1 failed in 0.12s ============================= -This test returns a failure report because ``func(3)`` does not return ``5``. +The ``[100%]`` refers to the overall progress of running all test cases. After it finishes, pytest then shows a failure report because ``func(3)`` does not return ``5``. .. note:: @@ -83,13 +88,18 @@ Run multiple tests Assert that a certain exception is raised -------------------------------------------------------------- -Use the :ref:`raises <assertraises>` helper to assert that some code raises an exception:: +Use the :ref:`raises <assertraises>` helper to assert that some code raises an exception: + +.. code-block:: python # content of test_sysexit.py import pytest + + def f(): raise SystemExit(1) + def test_mytest(): with pytest.raises(SystemExit): f() @@ -100,24 +110,32 @@ Execute the test function with “quiet” reporting mode: $ pytest -q test_sysexit.py . [100%] - 1 passed in 0.12 seconds + 1 passed in 0.12s + +.. note:: + + The ``-q/--quiet`` flag keeps the output brief in this and following examples. Group multiple tests in a class -------------------------------------------------------------- -Once you develop multiple tests, you may want to group them into a class. pytest makes it easy to create a class containing more than one test:: +.. regendoc:wipe + +Once you develop multiple tests, you may want to group them into a class. pytest makes it easy to create a class containing more than one test: + +.. code-block:: python # content of test_class.py - class TestClass(object): + class TestClass: def test_one(self): x = "this" - assert 'h' in x + assert "h" in x def test_two(self): x = "hello" - assert hasattr(x, 'check') + assert hasattr(x, "check") -``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery <test discovery>`, so it finds both ``test_`` prefixed functions. There is no need to subclass anything. We can simply run the module by passing its filename: +``pytest`` discovers all tests following its :ref:`Conventions for Python test discovery <test discovery>`, so it finds both ``test_`` prefixed functions. There is no need to subclass anything, but make sure to prefix your class with ``Test`` otherwise the class will be skipped. We can simply run the module by passing its filename: .. code-block:: pytest @@ -130,19 +148,74 @@ Once you develop multiple tests, you may want to group them into a class. pytest def test_two(self): x = "hello" - > assert hasattr(x, 'check') + > assert hasattr(x, "check") E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:8: AssertionError - 1 failed, 1 passed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_class.py::TestClass::test_two - AssertionError: assert False + 1 failed, 1 passed in 0.12s The first test passed and the second failed. You can easily see the intermediate values in the assertion to help you understand the reason for the failure. +Grouping tests in classes can be beneficial for the following reasons: + + * Test organization + * Sharing fixtures for tests only in that particular class + * Applying marks at the class level and having them implicitly apply to all tests + +Something to be aware of when grouping tests inside classes is that each test has a unique instance of the class. +Having each test share the same class instance would be very detrimental to test isolation and would promote poor test practices. +This is outlined below: + +.. regendoc:wipe + +.. code-block:: python + + # content of test_class_demo.py + class TestClassDemoInstance: + def test_one(self): + assert 0 + + def test_two(self): + assert 0 + + +.. code-block:: pytest + + $ pytest -k TestClassDemoInstance -q + FF [100%] + ================================= FAILURES ================================= + ______________________ TestClassDemoInstance.test_one ______________________ + + self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef> + + def test_one(self): + > assert 0 + E assert 0 + + test_class_demo.py:3: AssertionError + ______________________ TestClassDemoInstance.test_two ______________________ + + self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef> + + def test_two(self): + > assert 0 + E assert 0 + + test_class_demo.py:6: AssertionError + ========================= short test summary info ========================== + FAILED test_class_demo.py::TestClassDemoInstance::test_one - assert 0 + FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 + 2 failed in 0.12s + Request a unique temporary directory for functional tests -------------------------------------------------------------- -``pytest`` provides `Builtin fixtures/function arguments <https://docs.pytest.org/en/latest/builtin.html#builtinfixtures>`_ to request arbitrary resources, like a unique temporary directory:: +``pytest`` provides `Builtin fixtures/function arguments <https://docs.pytest.org/en/stable/builtin.html>`_ to request arbitrary resources, like a unique temporary directory: + +.. code-block:: python # content of test_tmpdir.py def test_needsfiles(tmpdir): @@ -168,7 +241,9 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look test_tmpdir.py:3: AssertionError --------------------------- Captured stdout call --------------------------- PYTEST_TMPDIR/test_needsfiles0 - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_tmpdir.py::test_needsfiles - assert 0 + 1 failed in 0.12s More info on tmpdir handling is available at :ref:`Temporary directories and files <tmpdir handling>`. @@ -191,5 +266,3 @@ Check out additional pytest resources to help you customize tests for your uniqu * ":ref:`fixtures`" for providing a functional baseline to your tests * ":ref:`plugins`" for managing and writing plugins * ":ref:`goodpractices`" for virtualenv and test layouts - -.. include:: links.inc diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/goodpractices.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/goodpractices.rst index f08bd5c4078..4b3c0af10a6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/goodpractices.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/goodpractices.rst @@ -1,4 +1,4 @@ -.. highlightlang:: python +.. highlight:: python .. _`goodpractices`: Good Integration Practices @@ -7,19 +7,22 @@ Good Integration Practices Install package with pip ------------------------------------------------- -For development, we recommend you use venv_ for virtual environments -(or virtualenv_ for Python 2.7) and +For development, we recommend you use venv_ for virtual environments and pip_ for installing your application and any dependencies, as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from your system Python installation. -Next, place a ``setup.py`` file in the root of your package with the following minimum content:: +Next, place a ``setup.py`` file in the root of your package with the following minimum content: + +.. code-block:: python from setuptools import setup, find_packages setup(name="PACKAGENAME", packages=find_packages()) -Where ``PACKAGENAME`` is the name of your package. You can then install your package in "editable" mode by running from the same directory:: +Where ``PACKAGENAME`` is the name of your package. You can then install your package in "editable" mode by running from the same directory: + +.. code-block:: bash pip install -e . @@ -61,7 +64,9 @@ Tests outside application code Putting tests into an extra directory outside your actual application code might be useful if you have many functional tests or for other reasons want -to keep tests separate from actual application code (often a good idea):: +to keep tests separate from actual application code (often a good idea): + +.. code-block:: text setup.py mypkg/ @@ -83,17 +88,20 @@ This has the following benefits: .. note:: - See :ref:`pythonpath` for more information about the difference between calling ``pytest`` and + See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and ``python -m pytest``. -Note that using this scheme your test files must have **unique names**, because +Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode <import-modes>` +(which is the default): your test files must have **unique names**, because ``pytest`` will import them as *top-level* modules since there are no packages to derive a full package name from. In other words, the test files in the example above will be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``. If you need to have test modules with the same name, you might add ``__init__.py`` files to your -``tests`` folder and subfolders, changing them to packages:: +``tests`` folder and subfolders, changing them to packages: + +.. code-block:: text setup.py mypkg/ @@ -111,11 +119,16 @@ Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test you to have modules with the same name. But now this introduces a subtle problem: in order to load the test modules from the ``tests`` directory, pytest prepends the root of the repository to ``sys.path``, which adds the side-effect that now ``mypkg`` is also importable. + This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment, because you want to test the *installed* version of your package, not the local code from the repository. +.. _`src-layout`: + In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a -sub-directory of your root:: +sub-directory of your root: + +.. code-block:: text setup.py src/ @@ -136,12 +149,23 @@ sub-directory of your root:: This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent `blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_. +.. note:: + The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have + any of the drawbacks above because ``sys.path`` and ``sys.modules`` are not changed when importing + test modules, so users that run + into this issue are strongly encouraged to try it and report if the new option works well for them. + + The ``src`` directory layout is still strongly recommended however. + + Tests as part of application code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Inlining test directories into your application package is useful if you have direct relation between tests and application modules and -want to distribute them along with your application:: +want to distribute them along with your application: + +.. code-block:: text setup.py mypkg/ @@ -154,7 +178,9 @@ want to distribute them along with your application:: test_view.py ... -In this scheme, it is easy to run your tests using the ``--pyargs`` option:: +In this scheme, it is easy to run your tests using the ``--pyargs`` option: + +.. code-block:: bash pytest --pyargs mypkg @@ -177,8 +203,8 @@ Note that this layout also works in conjunction with the ``src`` layout mentione .. note:: - If ``pytest`` finds an "a/b/test_module.py" test file while - recursing into the filesystem it determines the import name + In ``prepend`` and ``append`` import-modes, if pytest finds a ``"a/b/test_module.py"`` + test file while recursing into the filesystem it determines the import name as follows: * determine ``basedir``: this is the first "upward" (towards the root) @@ -199,6 +225,10 @@ Note that this layout also works in conjunction with the ``src`` layout mentione from each other and thus deriving a canonical import name helps to avoid surprises such as a test module getting imported twice. + With ``--import-mode=importlib`` things are less convoluted because + pytest doesn't need to change ``sys.path`` or ``sys.modules``, making things + much less surprising. + .. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _`buildout`: http://www.buildout.org/ @@ -219,102 +249,4 @@ options. It will run tests against the installed package and not against your source code checkout, helping to detect packaging glitches. - -Integrating with setuptools / ``python setup.py test`` / ``pytest-runner`` --------------------------------------------------------------------------- - -You can integrate test runs into your setuptools based project -with the `pytest-runner <https://pypi.org/project/pytest-runner/>`_ plugin. - -Add this to ``setup.py`` file: - -.. code-block:: python - - from setuptools import setup - - setup( - # ..., - setup_requires=["pytest-runner", ...], - tests_require=["pytest", ...], - # ..., - ) - - -And create an alias into ``setup.cfg`` file: - - -.. code-block:: ini - - [aliases] - test=pytest - -If you now type:: - - python setup.py test - -this will execute your tests using ``pytest-runner``. As this is a -standalone version of ``pytest`` no prior installation whatsoever is -required for calling the test command. You can also pass additional -arguments to pytest such as your test directory or other -options using ``--addopts``. - -You can also specify other pytest-ini options in your ``setup.cfg`` file -by putting them into a ``[tool:pytest]`` section: - -.. code-block:: ini - - [tool:pytest] - addopts = --verbose - python_files = testing/*/*.py - - -Manual Integration -^^^^^^^^^^^^^^^^^^ - -If for some reason you don't want/can't use ``pytest-runner``, you can write -your own setuptools Test command for invoking pytest. - -.. code-block:: python - - import sys - - from setuptools.command.test import test as TestCommand - - - class PyTest(TestCommand): - user_options = [("pytest-args=", "a", "Arguments to pass to pytest")] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.pytest_args = "" - - def run_tests(self): - import shlex - - # import here, cause outside the eggs aren't loaded - import pytest - - errno = pytest.main(shlex.split(self.pytest_args)) - sys.exit(errno) - - - setup( - # ..., - tests_require=["pytest"], - cmdclass={"pytest": PyTest}, - ) - -Now if you run:: - - python setup.py test - -this will download ``pytest`` if needed and then run your tests -as you would expect it to. You can pass a single string of arguments -using the ``--pytest-args`` or ``-a`` command-line option. For example:: - - python setup.py test -a "--durations=5" - -is equivalent to running ``pytest --durations=5``. - - -.. include:: links.inc +.. _`venv`: https://docs.python.org/3/library/venv.html diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst index c8403728f2f..4f8722c1c16 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/historical-notes.rst @@ -111,8 +111,8 @@ More details can be found in the `original PR <https://github.com/pytest-dev/pyt .. note:: - in a future major relase of pytest we will introduce class based markers, - at which point markers will no longer be limited to instances of :py:class:`Mark`. + in a future major release of pytest we will introduce class based markers, + at which point markers will no longer be limited to instances of :py:class:`~_pytest.mark.Mark`. cache plugin integrated into the core diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/img/favicon.png b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/img/favicon.png Binary files differnew file mode 100644 index 00000000000..5c8824d67d3 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/img/favicon.png diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/img/pytest1favi.ico b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/img/pytest1favi.ico Binary files differdeleted file mode 100644 index 6a34fe5c9f7..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/img/pytest1favi.ico +++ /dev/null diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/index.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/index.rst index edcbbeb84e7..a57e9bbacee 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/index.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/index.rst @@ -1,5 +1,11 @@ :orphan: +.. sidebar:: Next Open Trainings + + - `Professional testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via Python Academy, February 1-3 2021, Leipzig (Germany) and remote. + + Also see `previous talks and blogposts <talks.html>`_. + .. _features: pytest: helps you write better programs @@ -28,7 +34,7 @@ To execute it: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -44,7 +50,9 @@ To execute it: E + where 4 = inc(3) test_sample.py:6: AssertionError - ========================= 1 failed in 0.12 seconds ========================= + ========================= short test summary info ========================== + FAILED test_sample.py::test_answer - assert 4 == 5 + ============================ 1 failed in 0.12s ============================= Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See :ref:`Getting Started <getstarted>` for more examples. @@ -53,17 +61,17 @@ See :ref:`Getting Started <getstarted>` for more examples. Features -------- -- Detailed info on failing :ref:`assert statements <assert>` (no need to remember ``self.assert*`` names); +- Detailed info on failing :ref:`assert statements <assert>` (no need to remember ``self.assert*`` names) -- :ref:`Auto-discovery <test discovery>` of test modules and functions; +- :ref:`Auto-discovery <test discovery>` of test modules and functions -- :ref:`Modular fixtures <fixture>` for managing small or parametrized long-lived test resources; +- :ref:`Modular fixtures <fixture>` for managing small or parametrized long-lived test resources -- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box; +- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box -- Python 2.7, Python 3.4+, PyPy 2.3, Jython 2.5 (untested); +- Python 3.5+ and PyPy 3 -- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community; +- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community Documentation @@ -83,6 +91,39 @@ Changelog Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each version. +Support pytest +-------------- + +`Open Collective`_ is an online funding platform for open and transparent communities. +It provide tools to raise money and share your finances in full transparency. + +It is the platform of choice for individuals and companies that want to make one-time or +monthly donations directly to the project. + +See more details in the `pytest collective`_. + +.. _Open Collective: https://opencollective.com +.. _pytest collective: https://opencollective.com/pytest + + +pytest for enterprise +--------------------- + +Available as part of the Tidelift Subscription. + +The maintainers of pytest and thousands of other packages are working with Tidelift to deliver commercial support and +maintenance for the open source dependencies you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. + +`Learn more. <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_ + +Security +^^^^^^^^ + +pytest has never been associated with a security vulnerability, but in any case, to report a +security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_. +Tidelift will coordinate the fix and disclosure. + License ------- diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/links.inc b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/links.inc deleted file mode 100644 index 1b7cbd05b00..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/links.inc +++ /dev/null @@ -1,22 +0,0 @@ - -.. _`skipping plugin`: plugin/skipping.html -.. _`funcargs mechanism`: funcargs.html -.. _`doctest.py`: http://docs.python.org/library/doctest.html -.. _`xUnit style setup`: xunit_setup.html -.. _`pytest_nose`: plugin/nose.html -.. _`reStructured Text`: http://docutils.sourceforge.net -.. _`Python debugger`: http://docs.python.org/lib/module-pdb.html -.. _nose: https://nose.readthedocs.io/en/latest/ -.. _pytest: https://pypi.org/project/pytest/ -.. _mercurial: http://mercurial.selenic.com/wiki/ -.. _`setuptools`: https://pypi.org/project/setuptools/ -.. _`easy_install`: -.. _`distribute docs`: -.. _`distribute`: https://pypi.org/project/distribute/ -.. _`pip`: https://pypi.org/project/pip/ -.. _`venv`: https://docs.python.org/3/library/venv.html/ -.. _`virtualenv`: https://pypi.org/project/virtualenv/ -.. _hudson: http://hudson-ci.org/ -.. _jenkins: http://jenkins-ci.org/ -.. _tox: http://testrun.org/tox -.. _pylib: https://py.readthedocs.io/en/latest/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/logging.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/logging.rst index a53509184f6..52713854efb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/logging.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/logging.rst @@ -70,7 +70,9 @@ caplog fixture ^^^^^^^^^^^^^^ Inside tests it is possible to change the log level for the captured log -messages. This is supported by the ``caplog`` fixture:: +messages. This is supported by the ``caplog`` fixture: + +.. code-block:: python def test_foo(caplog): caplog.set_level(logging.INFO) @@ -78,59 +80,69 @@ messages. This is supported by the ``caplog`` fixture:: By default the level is set on the root logger, however as a convenience it is also possible to set the log level of any -logger:: +logger: + +.. code-block:: python def test_foo(caplog): - caplog.set_level(logging.CRITICAL, logger='root.baz') + caplog.set_level(logging.CRITICAL, logger="root.baz") pass The log levels set are restored automatically at the end of the test. It is also possible to use a context manager to temporarily change the log -level inside a ``with`` block:: +level inside a ``with`` block: + +.. code-block:: python def test_bar(caplog): with caplog.at_level(logging.INFO): pass Again, by default the level of the root logger is affected but the level of any -logger can be changed instead with:: +logger can be changed instead with: + +.. code-block:: python def test_bar(caplog): - with caplog.at_level(logging.CRITICAL, logger='root.baz'): + with caplog.at_level(logging.CRITICAL, logger="root.baz"): pass Lastly all the logs sent to the logger during the test run are made available on the fixture in the form of both the ``logging.LogRecord`` instances and the final log text. -This is useful for when you want to assert on the contents of a message:: +This is useful for when you want to assert on the contents of a message: + +.. code-block:: python def test_baz(caplog): func_under_test() for record in caplog.records: - assert record.levelname != 'CRITICAL' - assert 'wally' not in caplog.text + assert record.levelname != "CRITICAL" + assert "wally" not in caplog.text For all the available attributes of the log records see the ``logging.LogRecord`` class. You can also resort to ``record_tuples`` if all you want to do is to ensure, that certain messages have been logged under a given logger name with a given -severity and message:: +severity and message: + +.. code-block:: python def test_foo(caplog): - logging.getLogger().info('boo %s', 'arg') + logging.getLogger().info("boo %s", "arg") + + assert caplog.record_tuples == [("root", logging.INFO, "boo arg")] - assert caplog.record_tuples == [ - ('root', logging.INFO, 'boo arg'), - ] +You can call ``caplog.clear()`` to reset the captured log records in a test: -You can call ``caplog.clear()`` to reset the captured log records in a test:: +.. code-block:: python def test_something_with_clearing_records(caplog): some_method_that_creates_log_records() caplog.clear() your_test_method() - assert ['Foo'] == [rec.message for rec in caplog.records] + assert ["Foo"] == [rec.message for rec in caplog.records] The ``caplog.records`` attribute contains records from the current stage only, so @@ -149,7 +161,7 @@ the records for the ``setup`` and ``call`` stages during teardown like so: yield window for when in ("setup", "call"): messages = [ - x.message for x in caplog.get_records(when) if x.level == logging.WARNING + x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING ] if messages: pytest.fail( @@ -238,6 +250,9 @@ made in ``3.4`` after community feedback: * Log levels are no longer changed unless explicitly requested by the :confval:`log_level` configuration or ``--log-level`` command-line options. This allows users to configure logger objects themselves. + Setting :confval:`log_level` will set the level that is captured globally so if a specific test requires + a lower level than this, use the ``caplog.set_level()`` functionality otherwise that test will be prone to + failure. * :ref:`Live Logs <live_logs>` is now disabled by default and can be enabled setting the :confval:`log_cli` configuration option to ``true``. When enabled, the verbosity is increased so logging for each test is visible. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/mark.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/mark.rst index de6ab78222c..7370342a965 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/mark.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/mark.rst @@ -4,14 +4,19 @@ Marking test functions with attributes ====================================== By using the ``pytest.mark`` helper you can easily set -metadata on your test functions. There are -some builtin markers, for example: +metadata on your test functions. You can find the full list of builtin markers +in the :ref:`API Reference<marks ref>`. Or you can list all the markers, including +builtin and custom, using the CLI - :code:`pytest --markers`. +Here are some of the builtin markers: + +* :ref:`usefixtures <usefixtures>` - use fixtures on a test function or class +* :ref:`filterwarnings <filterwarnings>` - filter certain warnings of a test function * :ref:`skip <skip>` - always skip a test function * :ref:`skipif <skipif>` - skip a test function if a certain condition is met * :ref:`xfail <xfail>` - produce an "expected failure" outcome if a certain condition is met -* :ref:`parametrize <parametrizemark>` to perform multiple calls +* :ref:`parametrize <parametrizemark>` - perform multiple calls to the same test function. It's easy to create custom markers or to apply markers @@ -38,9 +43,19 @@ You can register custom marks in your ``pytest.ini`` file like this: slow: marks tests as slow (deselect with '-m "not slow"') serial -Note that everything after the ``:`` is an optional description. +or in your ``pyproject.toml`` file like this: + +.. code-block:: toml + + [tool.pytest.ini_options] + markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "serial", + ] + +Note that everything past the ``:`` after the mark name is an optional description. -Alternatively, you can register new markers programatically in a +Alternatively, you can register new markers programmatically in a :ref:`pytest_configure <initialization-hooks>` hook: .. code-block:: python @@ -61,7 +76,7 @@ Raising errors on unknown marks Unregistered marks applied with the ``@pytest.mark.name_of_the_mark`` decorator will always emit a warning in order to avoid silently doing something -surprising due to mis-typed names. As described in the previous section, you can disable +surprising due to mistyped names. As described in the previous section, you can disable the warning for custom marks by registering them in your ``pytest.ini`` file or using a custom ``pytest_configure`` hook. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/monkeypatch.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/monkeypatch.rst index c9304e0fb70..9480f008f7c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/monkeypatch.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/monkeypatch.rst @@ -8,46 +8,218 @@ Sometimes tests need to invoke functionality which depends on global settings or which invokes code which cannot be easily tested such as network access. The ``monkeypatch`` fixture helps you to safely set/delete an attribute, dictionary item or -environment variable or to modify ``sys.path`` for importing. +environment variable, or to modify ``sys.path`` for importing. + +The ``monkeypatch`` fixture provides these helper methods for safely patching and mocking +functionality in tests: + +.. code-block:: python + + monkeypatch.setattr(obj, name, value, raising=True) + monkeypatch.delattr(obj, name, raising=True) + monkeypatch.setitem(mapping, name, value) + monkeypatch.delitem(obj, name, raising=True) + monkeypatch.setenv(name, value, prepend=False) + monkeypatch.delenv(name, raising=True) + monkeypatch.syspath_prepend(path) + monkeypatch.chdir(path) + +All modifications will be undone after the requesting +test function or fixture has finished. The ``raising`` +parameter determines if a ``KeyError`` or ``AttributeError`` +will be raised if the target of the set/deletion operation does not exist. + +Consider the following scenarios: + +1. Modifying the behavior of a function or the property of a class for a test e.g. +there is an API call or database connection you will not make for a test but you know +what the expected output should be. Use :py:meth:`monkeypatch.setattr <MonkeyPatch.setattr>` to patch the +function or property with your desired testing behavior. This can include your own functions. +Use :py:meth:`monkeypatch.delattr <MonkeyPatch.delattr>` to remove the function or property for the test. + +2. Modifying the values of dictionaries e.g. you have a global configuration that +you want to modify for certain test cases. Use :py:meth:`monkeypatch.setitem <MonkeyPatch.setitem>` to patch the +dictionary for the test. :py:meth:`monkeypatch.delitem <MonkeyPatch.delitem>` can be used to remove items. + +3. Modifying environment variables for a test e.g. to test program behavior if an +environment variable is missing, or to set multiple values to a known variable. +:py:meth:`monkeypatch.setenv <MonkeyPatch.setenv>` and :py:meth:`monkeypatch.delenv <MonkeyPatch.delenv>` can be used for +these patches. + +4. Use ``monkeypatch.setenv("PATH", value, prepend=os.pathsep)`` to modify ``$PATH``, and +:py:meth:`monkeypatch.chdir <MonkeyPatch.chdir>` to change the context of the current working directory +during a test. + +5. Use :py:meth:`monkeypatch.syspath_prepend <MonkeyPatch.syspath_prepend>` to modify ``sys.path`` which will also +call ``pkg_resources.fixup_namespace_packages`` and :py:func:`importlib.invalidate_caches`. + See the `monkeypatch blog post`_ for some introduction material and a discussion of its motivation. .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ - Simple example: monkeypatching functions ---------------------------------------- -If you want to pretend that ``os.expanduser`` returns a certain -directory, you can use the :py:meth:`monkeypatch.setattr` method to -patch this function before calling into a function which uses it:: +Consider a scenario where you are working with user directories. In the context of +testing, you do not want your test to depend on the running user. ``monkeypatch`` +can be used to patch functions dependent on the user to always return a +specific value. + +In this example, :py:meth:`monkeypatch.setattr <MonkeyPatch.setattr>` is used to patch ``Path.home`` +so that the known testing path ``Path("/abc")`` is always used when the test is run. +This removes any dependency on the running user for testing purposes. +:py:meth:`monkeypatch.setattr <MonkeyPatch.setattr>` must be called before the function which will use +the patched function is called. +After the test function finishes the ``Path.home`` modification will be undone. - # content of test_module.py - import os.path - def getssh(): # pseudo application code - return os.path.join(os.path.expanduser("~admin"), '.ssh') +.. code-block:: python + + # contents of test_module.py with source code and the test + from pathlib import Path + + + def getssh(): + """Simple function to return expanded homedir ssh path.""" + return Path.home() / ".ssh" + + + def test_getssh(monkeypatch): + # mocked return function to replace Path.home + # always return '/abc' + def mockreturn(): + return Path("/abc") + + # Application of the monkeypatch to replace Path.home + # with the behavior of mockreturn defined above. + monkeypatch.setattr(Path, "home", mockreturn) - def test_mytest(monkeypatch): - def mockreturn(path): - return '/abc' - monkeypatch.setattr(os.path, 'expanduser', mockreturn) + # Calling getssh() will use mockreturn in place of Path.home + # for this test with the monkeypatch. x = getssh() - assert x == '/abc/.ssh' + assert x == Path("/abc/.ssh") + +Monkeypatching returned objects: building mock classes +------------------------------------------------------ + +:py:meth:`monkeypatch.setattr <MonkeyPatch.setattr>` can be used in conjunction with classes to mock returned +objects from functions instead of values. +Imagine a simple function to take an API url and return the json response. + +.. code-block:: python + + # contents of app.py, a simple API retrieval example + import requests + + + def get_json(url): + """Takes a URL, and returns the JSON.""" + r = requests.get(url) + return r.json() + +We need to mock ``r``, the returned response object for testing purposes. +The mock of ``r`` needs a ``.json()`` method which returns a dictionary. +This can be done in our test file by defining a class to represent ``r``. + +.. code-block:: python + + # contents of test_app.py, a simple test for our API retrieval + # import requests for the purposes of monkeypatching + import requests + + # our app.py that includes the get_json() function + # this is the previous code block example + import app + + # custom class to be the mock return value + # will override the requests.Response returned from requests.get + class MockResponse: + + # mock json() method always returns a specific testing dictionary + @staticmethod + def json(): + return {"mock_key": "mock_response"} + + + def test_get_json(monkeypatch): + + # Any arguments may be passed and mock_get() will always return our + # mocked object, which only has the .json() method. + def mock_get(*args, **kwargs): + return MockResponse() + + # apply the monkeypatch for requests.get to mock_get + monkeypatch.setattr(requests, "get", mock_get) + + # app.get_json, which contains requests.get, uses the monkeypatch + result = app.get_json("https://fakeurl") + assert result["mock_key"] == "mock_response" + + +``monkeypatch`` applies the mock for ``requests.get`` with our ``mock_get`` function. +The ``mock_get`` function returns an instance of the ``MockResponse`` class, which +has a ``json()`` method defined to return a known testing dictionary and does not +require any outside API connection. + +You can build the ``MockResponse`` class with the appropriate degree of complexity for +the scenario you are testing. For instance, it could include an ``ok`` property that +always returns ``True``, or return different values from the ``json()`` mocked method +based on input strings. + +This mock can be shared across tests using a ``fixture``: + +.. code-block:: python + + # contents of test_app.py, a simple test for our API retrieval + import pytest + import requests + + # app.py that includes the get_json() function + import app + + # custom class to be the mock return value of requests.get() + class MockResponse: + @staticmethod + def json(): + return {"mock_key": "mock_response"} + + + # monkeypatched requests.get moved to a fixture + @pytest.fixture + def mock_response(monkeypatch): + """Requests.get() mocked to return {'mock_key':'mock_response'}.""" + + def mock_get(*args, **kwargs): + return MockResponse() + + monkeypatch.setattr(requests, "get", mock_get) + + + # notice our test uses the custom fixture instead of monkeypatch directly + def test_get_json(mock_response): + result = app.get_json("https://fakeurl") + assert result["mock_key"] == "mock_response" + + +Furthermore, if the mock was designed to be applied to all tests, the ``fixture`` could +be moved to a ``conftest.py`` file and use the with ``autouse=True`` option. -Here our test function monkeypatches ``os.path.expanduser`` and -then calls into a function that calls it. After the test function -finishes the ``os.path.expanduser`` modification will be undone. Global patch example: preventing "requests" from remote operations ------------------------------------------------------------------ If you want to prevent the "requests" library from performing http -requests in all your tests, you can do:: +requests in all your tests, you can do: - # content of conftest.py +.. code-block:: python + + # contents of conftest.py import pytest + + @pytest.fixture(autouse=True) def no_requests(monkeypatch): + """Remove requests.sessions.Session.request for all tests.""" monkeypatch.delattr("requests.sessions.Session.request") This autouse fixture will be executed for each test function and it @@ -85,7 +257,7 @@ Monkeypatching environment variables ------------------------------------ If you are working with environment variables you often need to safely change the values -or delete them from the system for testing purposes. ``Monkeypatch`` provides a mechanism +or delete them from the system for testing purposes. ``monkeypatch`` provides a mechanism to do this using the ``setenv`` and ``delenv`` method. Our example code to test: .. code-block:: python @@ -96,11 +268,11 @@ to do this using the ``setenv`` and ``delenv`` method. Our example code to test: def get_os_user_lower(): """Simple retrieval function. - Returns lowercase USER or raises EnvironmentError.""" + Returns lowercase USER or raises OSError.""" username = os.getenv("USER") if username is None: - raise EnvironmentError("USER environment is not set.") + raise OSError("USER environment is not set.") return username.lower() @@ -121,16 +293,17 @@ both paths can be safely tested without impacting the running environment: def test_raise_exception(monkeypatch): - """Remove the USER env var and assert EnvironmentError is raised.""" + """Remove the USER env var and assert OSError is raised.""" monkeypatch.delenv("USER", raising=False) - with pytest.raises(EnvironmentError): + with pytest.raises(OSError): _ = get_os_user_lower() This behavior can be moved into ``fixture`` structures and shared across tests: .. code-block:: python + # contents of our test file e.g. test_code.py import pytest @@ -144,16 +317,122 @@ This behavior can be moved into ``fixture`` structures and shared across tests: monkeypatch.delenv("USER", raising=False) - # Notice the tests reference the fixtures for mocks + # notice the tests reference the fixtures for mocks def test_upper_to_lower(mock_env_user): assert get_os_user_lower() == "testinguser" def test_raise_exception(mock_env_missing): - with pytest.raises(EnvironmentError): + with pytest.raises(OSError): _ = get_os_user_lower() +Monkeypatching dictionaries +--------------------------- + +:py:meth:`monkeypatch.setitem <MonkeyPatch.setitem>` can be used to safely set the values of dictionaries +to specific values during tests. Take this simplified connection string example: + +.. code-block:: python + + # contents of app.py to generate a simple connection string + DEFAULT_CONFIG = {"user": "user1", "database": "db1"} + + + def create_connection_string(config=None): + """Creates a connection string from input or defaults.""" + config = config or DEFAULT_CONFIG + return f"User Id={config['user']}; Location={config['database']};" + +For testing purposes we can patch the ``DEFAULT_CONFIG`` dictionary to specific values. + +.. code-block:: python + + # contents of test_app.py + # app.py with the connection string function (prior code block) + import app + + + def test_connection(monkeypatch): + + # Patch the values of DEFAULT_CONFIG to specific + # testing values only for this test. + monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user") + monkeypatch.setitem(app.DEFAULT_CONFIG, "database", "test_db") + + # expected result based on the mocks + expected = "User Id=test_user; Location=test_db;" + + # the test uses the monkeypatched dictionary settings + result = app.create_connection_string() + assert result == expected + +You can use the :py:meth:`monkeypatch.delitem <MonkeyPatch.delitem>` to remove values. + +.. code-block:: python + + # contents of test_app.py + import pytest + + # app.py with the connection string function + import app + + + def test_missing_user(monkeypatch): + + # patch the DEFAULT_CONFIG t be missing the 'user' key + monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False) + + # Key error expected because a config is not passed, and the + # default is now missing the 'user' entry. + with pytest.raises(KeyError): + _ = app.create_connection_string() + + +The modularity of fixtures gives you the flexibility to define +separate fixtures for each potential mock and reference them in the needed tests. + +.. code-block:: python + + # contents of test_app.py + import pytest + + # app.py with the connection string function + import app + + # all of the mocks are moved into separated fixtures + @pytest.fixture + def mock_test_user(monkeypatch): + """Set the DEFAULT_CONFIG user to test_user.""" + monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user") + + + @pytest.fixture + def mock_test_database(monkeypatch): + """Set the DEFAULT_CONFIG database to test_db.""" + monkeypatch.setitem(app.DEFAULT_CONFIG, "database", "test_db") + + + @pytest.fixture + def mock_missing_default_user(monkeypatch): + """Remove the user key from DEFAULT_CONFIG""" + monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False) + + + # tests reference only the fixture mocks that are needed + def test_connection(mock_test_user, mock_test_database): + + expected = "User Id=test_user; Location=test_db;" + + result = app.create_connection_string() + assert result == expected + + + def test_missing_user(mock_missing_default_user): + + with pytest.raises(KeyError): + _ = app.create_connection_string() + .. currentmodule:: _pytest.monkeypatch diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/nose.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/nose.rst index 701ea44eca1..1ac70af6c6b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/nose.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/nose.rst @@ -3,8 +3,6 @@ Running tests written for nose ======================================= -.. include:: links.inc - ``pytest`` has basic support for running tests written for nose_. .. _nosestyle: @@ -28,7 +26,6 @@ Supported nose Idioms * setup and teardown at module/class/method level * SkipTest exceptions and markers * setup/teardown decorators -* ``yield``-based tests and their setup (considered deprecated as of pytest 3.0) * ``__test__`` attribute on modules/classes/functions * general usage of nose utilities @@ -46,7 +43,7 @@ Unsupported idioms / known issues <https://github.com/pytest-dev/pytest/issues/377/>`_. - nose imports test modules with the same import path (e.g. - ``tests.test_mod``) but different file system paths + ``tests.test_mode``) but different file system paths (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``) by extending sys.path/import semantics. pytest does not do that but there is discussion in `#268 <https://github.com/pytest-dev/pytest/issues/268>`_ for adding some support. Note that @@ -67,8 +64,8 @@ Unsupported idioms / known issues - no nose-configuration is recognized. -- ``yield``-based methods don't support ``setup`` properly because - the ``setup`` method is always called in the same class instance. - There are no plans to fix this currently because ``yield``-tests - are deprecated in pytest 3.0, with ``pytest.mark.parametrize`` - being the recommended alternative. +- ``yield``-based methods are unsupported as of pytest 4.1.0. They are + fundamentally incompatible with pytest because they don't support fixtures + properly since collection and test execution are separated. + +.. _nose: https://nose.readthedocs.io/en/latest/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/parametrize.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/parametrize.rst index 0abe7d70137..9e531ddd45d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/parametrize.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/parametrize.rst @@ -56,7 +56,7 @@ them in turn: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 3 items @@ -75,13 +75,25 @@ them in turn: E + where 54 = eval('6*9') test_expectation.py:6: AssertionError - ==================== 1 failed, 2 passed in 0.12 seconds ==================== + ========================= short test summary info ========================== + FAILED test_expectation.py::test_eval[6*9-42] - AssertionError: assert 54... + ======================= 1 failed, 2 passed in 0.12s ======================== + +.. note:: + + Parameter values are passed as-is to tests (no copy whatsoever). + + For example, if you pass a list or a dict as a parameter value, and + the test case code mutates it, the mutations will be reflected in subsequent + test case calls. .. note:: pytest by default escapes any non-ascii characters used in unicode strings for the parametrization because it has several downsides. - If however you would like to use unicode strings in parametrization and see them in the terminal as is (non-escaped), use this option in your ``pytest.ini``: + If however you would like to use unicode strings in parametrization + and see them in the terminal as is (non-escaped), use this option + in your ``pytest.ini``: .. code-block:: ini @@ -89,7 +101,8 @@ them in turn: disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True Keep in mind however that this might cause unwanted side effects and - even bugs depending on the OS used and plugins currently installed, so use it at your own risk. + even bugs depending on the OS used and plugins currently installed, + so use it at your own risk. As designed in this example, only one pair of input/output values fails @@ -121,17 +134,17 @@ Let's run this: $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 3 items test_expectation.py ..x [100%] - =================== 2 passed, 1 xfailed in 0.12 seconds ==================== + ======================= 2 passed, 1 xfailed in 0.12s ======================= The one parameter set which caused a failure previously now -shows up as an "xfailed (expected to fail)" test. +shows up as an "xfailed" (expected to fail) test. In case the values provided to ``parametrize`` result in an empty list - for example, if they're dynamically generated by some function - the behaviour of @@ -205,7 +218,7 @@ If we now pass two stringinput values, our test will run twice: $ pytest -q --stringinput="hello" --stringinput="world" test_strings.py .. [100%] - 2 passed in 0.12 seconds + 2 passed in 0.12s Let's also run with a stringinput that will lead to a failing test: @@ -225,7 +238,9 @@ Let's also run with a stringinput that will lead to a failing test: E + where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha test_strings.py:4: AssertionError - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_strings.py::test_valid_string[!] - AssertionError: assert False + 1 failed in 0.12s As expected our test function fails. @@ -239,7 +254,7 @@ list: s [100%] ========================= short test summary info ========================== SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:2 - 1 skipped in 0.12 seconds + 1 skipped in 0.12s Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across those sets cannot be duplicated, otherwise an error will be raised. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/plugins.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/plugins.rst index e80969193ab..855b597392b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/plugins.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/plugins.rst @@ -8,7 +8,9 @@ Installing and Using plugins This section talks about installing and using third party plugins. For writing your own plugins, please refer to :ref:`writing-plugins`. -Installing a third party plugin can be easily done with ``pip``:: +Installing a third party plugin can be easily done with ``pip``: + +.. code-block:: bash pip install pytest-NAME pip uninstall pytest-NAME @@ -39,8 +41,7 @@ Here is a little annotated list for some popular plugins: * `pytest-instafail <https://pypi.org/project/pytest-instafail/>`_: to report failures while the test run is happening. -* `pytest-bdd <https://pypi.org/project/pytest-bdd/>`_ and - `pytest-konira <https://pypi.org/project/pytest-konira/>`_ +* `pytest-bdd <https://pypi.org/project/pytest-bdd/>`_: to write tests using behaviour-driven testing. * `pytest-timeout <https://pypi.org/project/pytest-timeout/>`_: @@ -69,7 +70,7 @@ You may also discover more plugins through a `pytest- pypi.org search`_. Requiring/Loading plugins in a test module or conftest file ----------------------------------------------------------- -You can require plugins in a test module or a conftest file like this: +You can require plugins in a test module or a conftest file using :globalvar:`pytest_plugins`: .. code-block:: python @@ -79,6 +80,7 @@ When the test module or conftest plugin is loaded the specified plugins will be loaded as well. .. note:: + Requiring plugins using a ``pytest_plugins`` variable in non-root ``conftest.py`` files is deprecated. See :ref:`full explanation <requiring plugins in non-root conftests>` @@ -95,7 +97,9 @@ Finding out which plugins are active ------------------------------------ If you want to find out which plugins are active in your -environment you can type:: +environment you can type: + +.. code-block:: bash pytest --trace-config @@ -108,7 +112,9 @@ and their names. It will also print local plugins aka Deactivating / unregistering a plugin by name --------------------------------------------- -You can prevent plugins from loading or unregister them:: +You can prevent plugins from loading or unregister them: + +.. code-block:: bash pytest -p no:NAME diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/projects.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/projects.rst index 606e9d47c16..0720bc9dd7e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/projects.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/projects.rst @@ -28,7 +28,6 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref: * `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking * `Astropy <http://www.astropy.org/>`_ and `affiliated packages <http://www.astropy.org/affiliated/index.html>`_ * `tox <http://testrun.org/tox>`_, virtualenv/Hudson integration tool -* `PIDA <http://pida.co.uk>`_ framework for integrated development * `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager * `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB * `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities @@ -37,10 +36,10 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref: * `mwlib <https://pypi.org/project/mwlib/>`_ mediawiki parser and utility library * `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion * `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment -* `pylib <https://py.readthedocs.io>`_ cross-platform path, IO, dynamic code library -* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes +* `pylib <https://pylib.readthedocs.io/en/stable/>`_ cross-platform path, IO, dynamic code library * `bbfreeze <https://pypi.org/project/bbfreeze/>`_ create standalone executables from Python scripts -* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB +* `pdb++ <https://github.com/pdbpp/pdbpp>`_ a fancier version of PDB +* `pudb <https://github.com/inducer/pudb>`_ full-screen console debugger for python * `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem * `waskr <http://code.google.com/p/waskr/>`_ WSGI Stats Middleware * `guachi <http://code.google.com/p/guachi/>`_ global persistent configs for Python modules @@ -73,11 +72,10 @@ Some organisations using pytest ----------------------------------- * `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_ -* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests -* `Tandberg <http://www.tandberg.com/>`_ +* `Some Mozilla QA people <https://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin/>`_ use pytest to distribute their Selenium tests * `Shootq <http://web.shootq.com/>`_ * `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_ -* `cellzome <http://www.cellzome.com/>`_ +* cellzome * `Open End, Gothenborg <http://www.openend.se>`_ * `Laboratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_ * `merlinux, Germany <http://merlinux.eu>`_ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/py27-py34-deprecation.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/py27-py34-deprecation.rst index 0ca97539c82..f23f2062b79 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/py27-py34-deprecation.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/py27-py34-deprecation.rst @@ -1,26 +1,97 @@ -Python 2.7 and 3.4 support plan -=============================== +Python 2.7 and 3.4 support +========================== -Python 2.7 EOL is fast approaching, with -upstream support `ending in 2020 <https://legacy.python.org/dev/peps/pep-0373/#id4>`__. -Python 3.4's last release is scheduled for -`March 2019 <https://www.python.org/dev/peps/pep-0429/#release-schedule>`__. pytest is one of -the participating projects of the https://python3statement.org. +It is demanding on the maintainers of an open source project to support many Python versions, as +there's extra cost of keeping code compatible between all versions, while holding back on +features only made possible on newer Python versions. -The **pytest 4.6** series will be the last to support Python 2.7 and 3.4, and is scheduled -to be released by **mid-2019**. **pytest 5.0** and onwards will support only Python 3.5+. +In case of Python 2 and 3, the difference between the languages makes it even more prominent, +because many new Python 3 features cannot be used in a Python 2/3 compatible code base. -Thanks to the `python_requires`_ ``setuptools`` option, -Python 2.7 and Python 3.4 users using a modern ``pip`` version -will install the last pytest ``4.6`` version automatically even if ``5.0`` or later +Python 2.7 EOL has been reached `in 2020 <https://legacy.python.org/dev/peps/pep-0373/#id4>`__, with +the last release made in April, 2020. + +Python 3.4 EOL has been reached `in 2019 <https://www.python.org/dev/peps/pep-0429/#release-schedule>`__, with the last release made in March, 2019. + +For those reasons, in Jun 2019 it was decided that **pytest 4.6** series will be the last to support Python 2.7 and 3.4. + +What this means for general users +--------------------------------- + +Thanks to the `python_requires`_ setuptools option, +Python 2.7 and Python 3.4 users using a modern pip version +will install the last pytest 4.6.X version automatically even if 5.0 or later versions are available on PyPI. -While pytest ``5.0`` will be the new mainstream and development version, until **January 2020** -the pytest core team plans to make bug-fix releases of the pytest ``4.6`` series by -back-porting patches to the ``4.6.x`` branch that affect Python 2 users. +Users should ensure they are using the latest pip and setuptools versions for this to work. + +Maintenance of 4.6.X versions +----------------------------- + +Until January 2020, the pytest core team ported many bug-fixes from the main release into the +``4.6.x`` branch, with several 4.6.X releases being made along the year. + +From now on, the core team will **no longer actively backport patches**, but the ``4.6.x`` +branch will continue to exist so the community itself can contribute patches. + +The core team will be happy to accept those patches, and make new 4.6.X releases **until mid-2020** +(but consider that date as a ballpark, after that date the team might still decide to make new releases +for critical bugs). + +.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires + +Technical aspects +~~~~~~~~~~~~~~~~~ + +(This section is a transcript from `#5275 <https://github.com/pytest-dev/pytest/issues/5275>`__). + +In this section we describe the technical aspects of the Python 2.7 and 3.4 support plan. + +What goes into 4.6.X releases ++++++++++++++++++++++++++++++ + +New 4.6.X releases will contain bug fixes only. + +When will 4.6.X releases happen ++++++++++++++++++++++++++++++++ + +New 4.6.X releases will happen after we have a few bugs in place to release, or if a few weeks have +passed (say a single bug has been fixed a month after the latest 4.6.X release). + +No hard rules here, just ballpark. + +Who will handle applying bug fixes +++++++++++++++++++++++++++++++++++ + +We core maintainers expect that people still using Python 2.7/3.4 and being affected by +bugs to step up and provide patches and/or port bug fixes from the active branches. + +We will be happy to guide users interested in doing so, so please don't hesitate to ask. + +**Backporting changes into 4.6** + +Please follow these instructions: + +#. ``git fetch --all --prune`` + +#. ``git checkout origin/4.6.x -b backport-XXXX`` # use the PR number here + +#. Locate the merge commit on the PR, in the *merged* message, for example: + + nicoddemus merged commit 0f8b462 into pytest-dev:features + +#. ``git cherry-pick -m1 REVISION`` # use the revision you found above (``0f8b462``). + +#. Open a PR targeting ``4.6.x``: + + * Prefix the message with ``[4.6]`` so it is an obvious backport + * Delete the PR body, it usually contains a duplicate commit message. + +**Providing new PRs to 4.6** -**After 2020**, the core team will no longer actively backport patches, but the ``4.6.x`` -branch will continue to exist so the community itself can contribute patches. The core team will -be happy to accept those patches and make new ``4.6`` releases **until mid-2020**. +Fresh pull requests to ``4.6.x`` will be accepted provided that +the equivalent code in the active branches does not contain that bug (for example, a bug is specific +to Python 2 only). -.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires> +Bug fixes that also happen in the mainstream version should be first fixed +there, and then backported as per instructions above. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/pythonpath.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/pythonpath.rst index b6474276887..b8f4de9d95b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/pythonpath.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/pythonpath.rst @@ -3,11 +3,65 @@ pytest import mechanisms and ``sys.path``/``PYTHONPATH`` ======================================================== -Here's a list of scenarios where pytest may need to change ``sys.path`` in order -to import test modules or ``conftest.py`` files. +.. _`import-modes`: + +Import modes +------------ + +pytest as a testing framework needs to import test modules and ``conftest.py`` files for execution. + +Importing files in Python (at least until recently) is a non-trivial processes, often requiring +changing `sys.path <https://docs.python.org/3/library/sys.html#sys.path>`__. Some aspects of the +import process can be controlled through the ``--import-mode`` command-line flag, which can assume +these values: + +* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning* + of ``sys.path`` if not already there, and then imported with the `__import__ <https://docs.python.org/3/library/functions.html#__import__>`__ builtin. + + This requires test module names to be unique when the test directory tree is not arranged in + packages, because the modules will put in ``sys.modules`` after importing. + + This is the classic mechanism, dating back from the time Python 2 was still supported. + +* ``append``: the directory containing each module is appended to the end of ``sys.path`` if not already + there, and imported with ``__import__``. + + This better allows to run test modules against installed versions of a package even if the + package under test has the same import root. For example: + + :: + + testing/__init__.py + testing/test_pkg_under_test.py + pkg_under_test/ + + the tests will run against the installed version + of ``pkg_under_test`` when ``--import-mode=append`` is used whereas + with ``prepend`` they would pick up the local version. This kind of confusion is why + we advocate for using :ref:`src <src-layout>` layouts. + + Same as ``prepend``, requires test module names to be unique when the test directory tree is + not arranged in packages, because the modules will put in ``sys.modules`` after importing. + +* ``importlib``: new in pytest-6.0, this mode uses `importlib <https://docs.python.org/3/library/importlib.html>`__ to import test modules. This gives full control over the import process, and doesn't require + changing ``sys.path`` or ``sys.modules`` at all. + + For this reason this doesn't require test module names to be unique at all, but also makes test + modules non-importable by each other. This was made possible in previous modes, for tests not residing + in Python packages, because of the side-effects of changing ``sys.path`` and ``sys.modules`` + mentioned above. Users which require this should turn their tests into proper packages instead. + + We intend to make ``importlib`` the default in future releases. + +``prepend`` and ``append`` import modes scenarios +------------------------------------------------- + +Here's a list of scenarios when using ``prepend`` or ``append`` import modes where pytest needs to +change ``sys.path`` in order to import test modules or ``conftest.py`` files, and the issues users +might encounter because of that. Test modules / ``conftest.py`` files inside packages ----------------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider this file and directory layout:: @@ -22,11 +76,11 @@ Consider this file and directory layout:: |- test_foo.py -When executing:: - - pytest root/ +When executing: +.. code-block:: bash + pytest root/ pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a package given that there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the @@ -42,7 +96,7 @@ and allow test modules to have duplicated names. This is also discussed in detai :ref:`test discovery`. Standalone test modules / ``conftest.py`` files ------------------------------------------------ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider this file and directory layout:: @@ -54,7 +108,9 @@ Consider this file and directory layout:: |- test_foo.py -When executing:: +When executing: + +.. code-block:: bash pytest root/ @@ -68,9 +124,13 @@ imported in the global import namespace. This is also discussed in details in :ref:`test discovery`. +.. _`pytest vs python -m pytest`: + Invoking ``pytest`` versus ``python -m pytest`` ----------------------------------------------- -Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly -equivalent behaviour, except that the former call will add the current directory to ``sys.path``. +Running pytest with ``pytest [...]`` instead of ``python -m pytest [...]`` yields nearly +equivalent behaviour, except that the latter will add the current directory to ``sys.path``, which +is standard ``python`` behavior. + See also :ref:`cmdline`. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/reference.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/reference.rst index cc5fe8d8bf6..d1540a8ff2a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/reference.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/reference.rst @@ -1,5 +1,7 @@ -Reference -========= +.. _`reference`: + +API Reference +============= This page contains the full reference to pytest's API. @@ -13,39 +15,41 @@ Functions pytest.approx ~~~~~~~~~~~~~ -.. autofunction:: _pytest.python_api.approx +.. autofunction:: pytest.approx pytest.fail ~~~~~~~~~~~ **Tutorial**: :ref:`skipping` -.. autofunction:: _pytest.outcomes.fail +.. autofunction:: pytest.fail pytest.skip ~~~~~~~~~~~ -.. autofunction:: _pytest.outcomes.skip(msg, [allow_module_level=False]) +.. autofunction:: pytest.skip(msg, [allow_module_level=False]) + +.. _`pytest.importorskip ref`: pytest.importorskip ~~~~~~~~~~~~~~~~~~~ -.. autofunction:: _pytest.outcomes.importorskip +.. autofunction:: pytest.importorskip pytest.xfail ~~~~~~~~~~~~ -.. autofunction:: _pytest.outcomes.xfail +.. autofunction:: pytest.xfail pytest.exit ~~~~~~~~~~~ -.. autofunction:: _pytest.outcomes.exit +.. autofunction:: pytest.exit pytest.main ~~~~~~~~~~~ -.. autofunction:: _pytest.config.main +.. autofunction:: pytest.main pytest.param ~~~~~~~~~~~~ @@ -57,7 +61,7 @@ pytest.raises **Tutorial**: :ref:`assertraises`. -.. autofunction:: pytest.raises(expected_exception: Exception, [match], [message]) +.. autofunction:: pytest.raises(expected_exception: Exception [, *, match]) :with: excinfo pytest.deprecated_call @@ -122,7 +126,7 @@ Add warning filters to marked test items. .. code-block:: python - @pytest.mark.warnings("ignore:.*usage will be deprecated.*:DeprecationWarning") + @pytest.mark.filterwarnings("ignore:.*usage will be deprecated.*:DeprecationWarning") def test_foo(): ... @@ -134,7 +138,7 @@ pytest.mark.parametrize **Tutorial**: :doc:`parametrize`. -.. automethod:: _pytest.python.Metafunc.parametrize +This mark has the same signature as :py:meth:`_pytest.python.Metafunc.parametrize`; see there. .. _`pytest.mark.skip ref`: @@ -176,14 +180,17 @@ pytest.mark.usefixtures Mark a test function as using the given fixture names. -.. warning:: +.. py:function:: pytest.mark.usefixtures(*names) - This mark has no effect when applied - to a **fixture** function. + :param args: The names of the fixture to use, as strings. -.. py:function:: pytest.mark.usefixtures(*names) +.. note:: + + When using `usefixtures` in hooks, it can only load fixtures when applied to a test function before test setup + (for example in the `pytest_collection_modifyitems` hook). + + Also not that his mark has no effect when applied to **fixtures**. - :param args: the names of the fixture to use, as strings .. _`pytest.mark.xfail ref`: @@ -200,9 +207,12 @@ Marks a test function as *expected to fail*. :type condition: bool or str :param condition: Condition for marking the test function as xfail (``True/False`` or a - :ref:`condition string <string conditions>`). - :keyword str reason: Reason why the test function is marked as xfail. - :keyword Exception raises: Exception subclass expected to be raised by the test function; other exceptions will fail the test. + :ref:`condition string <string conditions>`). If a bool, you also have + to specify ``reason`` (see :ref:`condition string <string conditions>`). + :keyword str reason: + Reason why the test function is marked as xfail. + :keyword Type[Exception] raises: + Exception subclass expected to be raised by the test function; other exceptions will fail the test. :keyword bool run: If the test function should actually be executed. If ``False``, the function will always xfail and will not be executed (useful if a function is segfaulting). @@ -216,7 +226,7 @@ Marks a test function as *expected to fail*. a new release of a library fixes a known bug). -custom marks +Custom marks ~~~~~~~~~~~~ Marks are created dynamically using the factory object ``pytest.mark`` and applied as a decorator. @@ -230,7 +240,7 @@ For example: ... Will create and attach a :class:`Mark <_pytest.mark.structures.Mark>` object to the collected -:class:`Item <_pytest.nodes.Item>`, which can then be accessed by fixtures or hooks with +:class:`Item <pytest.Item>`, which can then be accessed by fixtures or hooks with :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>`. The ``mark`` object will have the following attributes: .. code-block:: python @@ -239,6 +249,8 @@ Will create and attach a :class:`Mark <_pytest.mark.structures.Mark>` object to mark.kwargs == {"method": "thread"} +.. _`fixtures-api`: + Fixtures -------- @@ -269,6 +281,8 @@ Example of a fixture requiring another fixture: For more details, consult the full :ref:`fixtures docs <fixture>`. +.. _`pytest.fixture-api`: + @pytest.fixture ~~~~~~~~~~~~~~~ @@ -276,7 +290,7 @@ For more details, consult the full :ref:`fixtures docs <fixture>`. :decorator: -.. _`cache-api`: +.. fixture:: cache config.cache ~~~~~~~~~~~~ @@ -297,6 +311,8 @@ Under the hood, the cache plugin uses the simple .. automethod:: Cache.makedir +.. fixture:: capsys + capsys ~~~~~~ @@ -322,6 +338,8 @@ capsys :members: +.. fixture:: capsysbinary + capsysbinary ~~~~~~~~~~~~ @@ -342,6 +360,8 @@ capsysbinary assert captured.out == b"hello\n" +.. fixture:: capfd + capfd ~~~~~~ @@ -358,10 +378,12 @@ capfd def test_system_echo(capfd): os.system('echo "hello"') - captured = capsys.readouterr() + captured = capfd.readouterr() assert captured.out == "hello\n" +.. fixture:: capfdbinary + capfdbinary ~~~~~~~~~~~~ @@ -382,6 +404,8 @@ capfdbinary assert captured.out == b"hello\n" +.. fixture:: doctest_namespace + doctest_namespace ~~~~~~~~~~~~~~~~~ @@ -400,6 +424,8 @@ doctest_namespace For more details: :ref:`doctest_namespace`. +.. fixture:: request + request ~~~~~~~ @@ -411,12 +437,16 @@ The ``request`` fixture is a special fixture providing information of the reques :members: +.. fixture:: pytestconfig + pytestconfig ~~~~~~~~~~~~ .. autofunction:: _pytest.fixtures.pytestconfig() +.. fixture:: record_property + record_property ~~~~~~~~~~~~~~~~~~~ @@ -425,6 +455,8 @@ record_property .. autofunction:: _pytest.junitxml.record_property() +.. fixture:: record_testsuite_property + record_testsuite_property ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -432,6 +464,9 @@ record_testsuite_property .. autofunction:: _pytest.junitxml.record_testsuite_property() + +.. fixture:: caplog + caplog ~~~~~~ @@ -440,12 +475,14 @@ caplog .. autofunction:: _pytest.logging.caplog() :no-auto-options: - This returns a :class:`_pytest.logging.LogCaptureFixture` instance. + Returns a :class:`_pytest.logging.LogCaptureFixture` instance. .. autoclass:: _pytest.logging.LogCaptureFixture :members: +.. fixture:: monkeypatch + monkeypatch ~~~~~~~~~~~ @@ -456,11 +493,14 @@ monkeypatch .. autofunction:: _pytest.monkeypatch.monkeypatch() :no-auto-options: - This returns a :class:`MonkeyPatch` instance. + Returns a :class:`MonkeyPatch` instance. .. autoclass:: _pytest.monkeypatch.MonkeyPatch :members: + +.. fixture:: testdir + testdir ~~~~~~~ @@ -469,9 +509,11 @@ testdir This fixture provides a :class:`Testdir` instance useful for black-box testing of test files, making it ideal to test plugins. -To use it, include in your top-most ``conftest.py`` file:: +To use it, include in your top-most ``conftest.py`` file: + +.. code-block:: python - pytest_plugins = 'pytester' + pytest_plugins = "pytester" @@ -485,6 +527,8 @@ To use it, include in your top-most ``conftest.py`` file:: :members: +.. fixture:: recwarn + recwarn ~~~~~~~ @@ -495,19 +539,18 @@ recwarn .. autofunction:: recwarn() :no-auto-options: -.. autoclass:: _pytest.recwarn.WarningsRecorder() +.. autoclass:: WarningsRecorder() :members: Each recorded warning is an instance of :class:`warnings.WarningMessage`. .. note:: - :class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1 - -.. note:: ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated differently; see :ref:`ensuring_function_triggers`. +.. fixture:: tmp_path + tmp_path ~~~~~~~~ @@ -519,6 +562,8 @@ tmp_path :no-auto-options: +.. fixture:: tmp_path_factory + tmp_path_factory ~~~~~~~~~~~~~~~~ @@ -534,6 +579,8 @@ tmp_path_factory .. automethod:: TempPathFactory.getbasetemp +.. fixture:: tmpdir + tmpdir ~~~~~~ @@ -545,6 +592,8 @@ tmpdir :no-auto-options: +.. fixture:: tmpdir_factory + tmpdir_factory ~~~~~~~~~~~~~~ @@ -597,31 +646,6 @@ Initialization hooks called for plugins and ``conftest.py`` files. .. autofunction:: pytest_plugin_registered -Test running hooks -~~~~~~~~~~~~~~~~~~ - -All runtest related hooks receive a :py:class:`pytest.Item <_pytest.main.Item>` object. - -.. autofunction:: pytest_runtestloop -.. autofunction:: pytest_runtest_protocol -.. autofunction:: pytest_runtest_logstart -.. autofunction:: pytest_runtest_logfinish -.. autofunction:: pytest_runtest_setup -.. autofunction:: pytest_runtest_call -.. autofunction:: pytest_runtest_teardown -.. autofunction:: pytest_runtest_makereport - -For deeper understanding you may look at the default implementation of -these hooks in :py:mod:`_pytest.runner` and maybe also -in :py:mod:`_pytest.pdb` which interacts with :py:mod:`_pytest.capture` -and its input/output capturing in order to immediately drop -into interactive debugging when a test failure occurs. - -The :py:mod:`_pytest.terminal` reported specifically uses -the reporting hook to print information about a test run. - -.. autofunction:: pytest_pyfunc_call - Collection hooks ~~~~~~~~~~~~~~~~ @@ -629,7 +653,6 @@ Collection hooks .. autofunction:: pytest_collection .. autofunction:: pytest_ignore_collect -.. autofunction:: pytest_collect_directory .. autofunction:: pytest_collect_file .. autofunction:: pytest_pycollect_makemodule @@ -647,6 +670,28 @@ items, delete or otherwise amend the test items: .. autofunction:: pytest_collection_finish +Test running (runtest) hooks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All runtest related hooks receive a :py:class:`pytest.Item <pytest.Item>` object. + +.. autofunction:: pytest_runtestloop +.. autofunction:: pytest_runtest_protocol +.. autofunction:: pytest_runtest_logstart +.. autofunction:: pytest_runtest_logfinish +.. autofunction:: pytest_runtest_setup +.. autofunction:: pytest_runtest_call +.. autofunction:: pytest_runtest_teardown +.. autofunction:: pytest_runtest_makereport + +For deeper understanding you may look at the default implementation of +these hooks in ``_pytest.runner`` and maybe also +in ``_pytest.pdb`` which interacts with ``_pytest.capture`` +and its input/output capturing in order to immediately drop +into interactive debugging when a test failure occurs. + +.. autofunction:: pytest_pyfunc_call + Reporting hooks ~~~~~~~~~~~~~~~ @@ -664,16 +709,16 @@ Session related reporting hooks: .. autofunction:: pytest_fixture_setup .. autofunction:: pytest_fixture_post_finalizer .. autofunction:: pytest_warning_captured +.. autofunction:: pytest_warning_recorded -And here is the central hook for reporting about -test execution: +Central hook for reporting about test execution: .. autofunction:: pytest_runtest_logreport -You can also use this hook to customize assertion representation for some -types: +Assertion related hooks: .. autofunction:: pytest_assertrepr_compare +.. autofunction:: pytest_assertion_pass Debugging/Interaction hooks @@ -704,17 +749,25 @@ CallInfo Class ~~~~~ -.. autoclass:: _pytest.python.Class() +.. autoclass:: pytest.Class() :members: :show-inheritance: Collector ~~~~~~~~~ -.. autoclass:: _pytest.nodes.Collector() +.. autoclass:: pytest.Collector() :members: :show-inheritance: +CollectReport +~~~~~~~~~~~~~ + +.. autoclass:: _pytest.reports.CollectReport() + :members: + :show-inheritance: + :inherited-members: + Config ~~~~~~ @@ -727,6 +780,21 @@ ExceptionInfo .. autoclass:: _pytest._code.ExceptionInfo :members: + +ExitCode +~~~~~~~~ + +.. autoclass:: pytest.ExitCode + :members: + +File +~~~~ + +.. autoclass:: pytest.File() + :members: + :show-inheritance: + + FixtureDef ~~~~~~~~~~ @@ -744,14 +812,14 @@ FSCollector Function ~~~~~~~~ -.. autoclass:: _pytest.python.Function() +.. autoclass:: pytest.Function() :members: :show-inheritance: Item ~~~~ -.. autoclass:: _pytest.nodes.Item() +.. autoclass:: pytest.Item() :members: :show-inheritance: @@ -785,7 +853,7 @@ Metafunc Module ~~~~~~ -.. autoclass:: _pytest.python.Module() +.. autoclass:: pytest.Module() :members: :show-inheritance: @@ -801,12 +869,6 @@ Parser .. autoclass:: _pytest.config.argparsing.Parser() :members: -PluginManager -~~~~~~~~~~~~~ - -.. autoclass:: pluggy.PluginManager() - :members: - PytestPluginManager ~~~~~~~~~~~~~~~~~~~ @@ -814,36 +876,41 @@ PytestPluginManager .. autoclass:: _pytest.config.PytestPluginManager() :members: :undoc-members: + :inherited-members: :show-inheritance: Session ~~~~~~~ -.. autoclass:: _pytest.main.Session() +.. autoclass:: pytest.Session() :members: :show-inheritance: TestReport ~~~~~~~~~~ -.. autoclass:: _pytest.runner.TestReport() +.. autoclass:: _pytest.reports.TestReport() :members: + :show-inheritance: :inherited-members: _Result ~~~~~~~ +Result used within :ref:`hook wrappers <hookwrapper>`. + .. autoclass:: pluggy.callers._Result - :members: +.. automethod:: pluggy.callers._Result.get_result +.. automethod:: pluggy.callers._Result.force_result -Special Variables ------------------ +Global Variables +---------------- -pytest treats some global variables in a special manner when defined in a test module. +pytest treats some global variables in a special manner when defined in a test module or +``conftest.py`` files. -collect_ignore -~~~~~~~~~~~~~~ +.. globalvar:: collect_ignore **Tutorial**: :ref:`customizing-test-collection` @@ -855,8 +922,7 @@ Needs to be ``list[str]``. collect_ignore = ["setup.py"] -collect_ignore_glob -~~~~~~~~~~~~~~~~~~~ +.. globalvar:: collect_ignore_glob **Tutorial**: :ref:`customizing-test-collection` @@ -869,8 +935,7 @@ contain glob patterns. collect_ignore_glob = ["*_ignore.py"] -pytest_plugins -~~~~~~~~~~~~~~ +.. globalvar:: pytest_plugins **Tutorial**: :ref:`available installable plugins` @@ -886,13 +951,12 @@ Can be either a ``str`` or ``Sequence[str]``. pytest_plugins = ("myapp.testsupport.tools", "myapp.testsupport.regression") -pytest_mark -~~~~~~~~~~~ +.. globalvar:: pytestmark **Tutorial**: :ref:`scoped-marking` Can be declared at the **global** level in *test modules* to apply one or more :ref:`marks <marks ref>` to all -test functions and methods. Can be either a single mark or a list of marks. +test functions and methods. Can be either a single mark or a list of marks (applied in left-to-right order). .. code-block:: python @@ -907,31 +971,32 @@ test functions and methods. Can be either a single mark or a list of marks. pytestmark = [pytest.mark.integration, pytest.mark.slow] -PYTEST_DONT_REWRITE (module docstring) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The text ``PYTEST_DONT_REWRITE`` can be add to any **module docstring** to disable -:ref:`assertion rewriting <assert introspection>` for that module. - Environment Variables --------------------- Environment variables that can be used to change pytest's behavior. -PYTEST_ADDOPTS -~~~~~~~~~~~~~~ +.. envvar:: PYTEST_ADDOPTS This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given by the user, see :ref:`adding default options` for more information. -PYTEST_DEBUG -~~~~~~~~~~~~ +.. envvar:: PYTEST_CURRENT_TEST + +This is not meant to be set by users, but is set by pytest internally with the name of the current test so other +processes can inspect it, see :ref:`pytest current test env` for more information. + +.. envvar:: PYTEST_DEBUG When set, pytest will print tracing and debug information. -PYTEST_PLUGINS -~~~~~~~~~~~~~~ +.. envvar:: PYTEST_DISABLE_PLUGIN_AUTOLOAD + +When set, disables plugin auto-loading through setuptools entrypoints. Only explicitly specified plugins will be +loaded. + +.. envvar:: PYTEST_PLUGINS Contains comma-separated list of modules that should be loaded as plugins: @@ -939,17 +1004,65 @@ Contains comma-separated list of modules that should be loaded as plugins: export PYTEST_PLUGINS=mymodule.plugin,xdist -PYTEST_DISABLE_PLUGIN_AUTOLOAD -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. envvar:: PY_COLORS -When set, disables plugin auto-loading through setuptools entrypoints. Only explicitly specified plugins will be -loaded. +When set to ``1``, pytest will use color in terminal output. +When set to ``0``, pytest will not use color. +``PY_COLORS`` takes precedence over ``NO_COLOR`` and ``FORCE_COLOR``. -PYTEST_CURRENT_TEST -~~~~~~~~~~~~~~~~~~~ +.. envvar:: NO_COLOR -This is not meant to be set by users, but is set by pytest internally with the name of the current test so other -processes can inspect it, see :ref:`pytest current test env` for more information. +When set (regardless of value), pytest will not use color in terminal output. +``PY_COLORS`` takes precedence over ``NO_COLOR``, which takes precedence over ``FORCE_COLOR``. +See `no-color.org <https://no-color.org/>`__ for other libraries supporting this community standard. + +.. envvar:: FORCE_COLOR + +When set (regardless of value), pytest will use color in terminal output. +``PY_COLORS`` and ``NO_COLOR`` take precedence over ``FORCE_COLOR``. + +Exceptions +---------- + +.. autoclass:: pytest.UsageError() + :show-inheritance: + +.. _`warnings ref`: + +Warnings +-------- + +Custom warnings generated in some situations such as improper usage or deprecated features. + +.. autoclass:: pytest.PytestWarning + :show-inheritance: + +.. autoclass:: pytest.PytestAssertRewriteWarning + :show-inheritance: + +.. autoclass:: pytest.PytestCacheWarning + :show-inheritance: + +.. autoclass:: pytest.PytestCollectionWarning + :show-inheritance: + +.. autoclass:: pytest.PytestConfigWarning + :show-inheritance: + +.. autoclass:: pytest.PytestDeprecationWarning + :show-inheritance: + +.. autoclass:: pytest.PytestExperimentalApiWarning + :show-inheritance: + +.. autoclass:: pytest.PytestUnhandledCoroutineWarning + :show-inheritance: + +.. autoclass:: pytest.PytestUnknownMarkWarning + :show-inheritance: + + +Consult the :ref:`internal-warnings` section in the documentation for more information. .. _`ini options ref`: @@ -957,17 +1070,17 @@ processes can inspect it, see :ref:`pytest current test env` for more informatio Configuration Options --------------------- -Here is a list of builtin configuration options that may be written in a ``pytest.ini``, ``tox.ini`` or ``setup.cfg`` -file, usually located at the root of your repository. All options must be under a ``[pytest]`` section -(``[tool:pytest]`` for ``setup.cfg`` files). +Here is a list of builtin configuration options that may be written in a ``pytest.ini``, ``pyproject.toml``, ``tox.ini`` or ``setup.cfg`` +file, usually located at the root of your repository. To see each file format in details, see +:ref:`config file formats`. .. warning:: - Usage of ``setup.cfg`` is not recommended unless for very simple use cases. ``.cfg`` + Usage of ``setup.cfg`` is not recommended except for very simple use cases. ``.cfg`` files use a different parser than ``pytest.ini`` and ``tox.ini`` which might cause hard to track down problems. - When possible, it is recommended to use the latter files to hold your pytest configuration. + When possible, it is recommended to use the latter files, or ``pyproject.toml``, to hold your pytest configuration. -Configuration file options may be overwritten in the command-line by using ``-o/--override``, which can also be +Configuration options may be overwritten in the command-line by using ``-o/--override-ini``, which can also be passed multiple times. The expected format is ``name=value``. For example:: pytest -o console_output_style=classic -o cache_dir=/tmp/mycache @@ -984,7 +1097,9 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] addopts = --maxfail=2 -rf # exit after 2 failures, report fail info - issuing ``pytest test_hello.py`` actually means:: + issuing ``pytest test_hello.py`` actually means: + + .. code-block:: bash pytest --maxfail=2 -rf test_hello.py @@ -993,8 +1108,6 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: cache_dir - - Sets a directory where stores content of cache plugin. Default directory is ``.pytest_cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be relative or absolute path. If setting relative path, then directory is created @@ -1068,6 +1181,23 @@ passed multiple times. The expected format is ``name=value``. For example:: for more details. +.. confval:: faulthandler_timeout + + Dumps the tracebacks of all threads if a test takes longer than ``X`` seconds to run (including + fixture setup and teardown). Implemented using the `faulthandler.dump_traceback_later`_ function, + so all caveats there apply. + + .. code-block:: ini + + # content of pytest.ini + [pytest] + faulthandler_timeout=5 + + For more information please refer to :ref:`faulthandler`. + +.. _`faulthandler.dump_traceback_later`: https://docs.python.org/3/library/faulthandler.html#faulthandler.dump_traceback_later + + .. confval:: filterwarnings @@ -1122,9 +1252,17 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: junit_logging .. versionadded:: 3.5 + .. versionchanged:: 5.4 + ``log``, ``all``, ``out-err`` options added. + + Configures if captured output should be written to the JUnit XML file. Valid values are: - Configures if stdout/stderr should be written to the JUnit XML file. Valid values are - ``system-out``, ``system-err``, and ``no`` (the default). + * ``log``: write only ``logging`` captured output. + * ``system-out``: write captured ``stdout`` contents. + * ``system-err``: write captured ``stderr`` contents. + * ``out-err``: write both captured ``stdout`` and ``stderr`` contents. + * ``all``: write captured ``logging``, ``stdout`` and ``stderr`` contents. + * ``no`` (the default): no captured output is written. .. code-block:: ini @@ -1154,6 +1292,38 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] junit_suite_name = my_suite +.. confval:: log_auto_indent + + Allow selective auto-indentation of multiline log messages. + + Supports command line option ``--log-auto-indent [value]`` + and config option ``log_auto_indent = [value]`` to set the + auto-indentation behavior for all logging. + + ``[value]`` can be: + * True or "On" - Dynamically auto-indent multiline log messages + * False or "Off" or 0 - Do not auto-indent multiline log messages (the default behavior) + * [positive integer] - auto-indent multiline log messages by [value] spaces + + .. code-block:: ini + + [pytest] + log_auto_indent = False + + Supports passing kwarg ``extra={"auto_indent": [value]}`` to + calls to ``logging.log()`` to specify auto-indentation behavior for + a specific entry in the log. ``extra`` kwarg overrides the value specified + on the command line or in the config. + +.. confval:: log_cli + + Enable log display during test run (also known as :ref:`"live logging" <live_logs>`). + The default is ``False``. + + .. code-block:: ini + + [pytest] + log_cli = True .. confval:: log_cli_date_format @@ -1296,20 +1466,6 @@ passed multiple times. The expected format is ``name=value``. For example:: For more information, see :ref:`logging`. -.. confval:: log_print - - - - If set to ``False``, will disable displaying captured logging messages for failed tests. - - .. code-block:: ini - - [pytest] - log_print = False - - For more information, see :ref:`logging`. - - .. confval:: markers When the ``--strict-markers`` or ``--strict`` command-line arguments are used, @@ -1327,6 +1483,11 @@ passed multiple times. The expected format is ``name=value``. For example:: slow serial + .. note:: + The use of ``--strict-markers`` is highly preferred. ``--strict`` was kept for + backward compatibility only and may be confusing for others as it only applies to + markers and not to other options. + .. confval:: minversion Specifies a minimal pytest version required for running tests. @@ -1435,6 +1596,19 @@ passed multiple times. The expected format is ``name=value``. For example:: See :ref:`change naming conventions` for more detailed examples. +.. confval:: required_plugins + + A space separated list of plugins that must be present for pytest to run. + Plugins can be listed with or without version specifiers directly following + their name. Whitespace between different version specifiers is not allowed. + If any one of the plugins is not found, emit an error. + + .. code-block:: ini + + [pytest] + required_plugins = pytest-django>=3.0.0,<4.0.0 pytest-html pytest-xdist>=1.0.0 + + .. confval:: testpaths @@ -1478,3 +1652,296 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] xfail_strict = True + + +.. _`command-line-flags`: + +Command-line Flags +------------------ + +All the command-line flags can be obtained by running ``pytest --help``:: + + $ pytest --help + usage: pytest [options] [file_or_dir] [file_or_dir] [...] + + positional arguments: + file_or_dir + + general: + -k EXPRESSION only run tests which match the given substring + expression. An expression is a python evaluatable + expression where all names are substring-matched + against test names and their parent classes. + Example: -k 'test_method or test_other' matches all + test functions and classes whose name contains + 'test_method' or 'test_other', while -k 'not + test_method' matches those that don't contain + 'test_method' in their names. -k 'not test_method + and not test_other' will eliminate the matches. + Additionally keywords are matched to classes and + functions containing extra names in their + 'extra_keyword_matches' set, as well as functions + which have names assigned directly to them. The + matching is case-insensitive. + -m MARKEXPR only run tests matching given mark expression. + For example: -m 'mark1 and not mark2'. + --markers show markers (builtin, plugin and per-project ones). + -x, --exitfirst exit instantly on first error or failed test. + --fixtures, --funcargs + show available fixtures, sorted by plugin appearance + (fixtures with leading '_' are only shown with '-v') + --fixtures-per-test show fixtures per test + --pdb start the interactive Python debugger on errors or + KeyboardInterrupt. + --pdbcls=modulename:classname + start a custom interactive Python debugger on + errors. For example: + --pdbcls=IPython.terminal.debugger:TerminalPdb + --trace Immediately break when running each test. + --capture=method per-test capturing method: one of fd|sys|no|tee-sys. + -s shortcut for --capture=no. + --runxfail report the results of xfail tests as if they were + not marked + --lf, --last-failed rerun only the tests that failed at the last run (or + all if none failed) + --ff, --failed-first run all tests, but run the last failures first. + This may re-order tests and thus lead to repeated + fixture setup/teardown. + --nf, --new-first run tests from new files first, then the rest of the + tests sorted by file mtime + --cache-show=[CACHESHOW] + show cache contents, don't perform collection or + tests. Optional argument: glob (default: '*'). + --cache-clear remove all cache contents at start of test run. + --lfnf={all,none}, --last-failed-no-failures={all,none} + which tests to run with no previously (known) + failures. + --sw, --stepwise exit on test failure and continue from last failing + test next time + --stepwise-skip ignore the first failing test but stop on the next + failing test + + reporting: + --durations=N show N slowest setup/test durations (N=0 for all). + --durations-min=N Minimal duration in seconds for inclusion in slowest + list. Default 0.005 + -v, --verbose increase verbosity. + --no-header disable header + --no-summary disable summary + -q, --quiet decrease verbosity. + --verbosity=VERBOSE set verbosity. Default is 0. + -r chars show extra test summary info as specified by chars: + (f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed, + (p)assed, (P)assed with output, (a)ll except passed + (p/P), or (A)ll. (w)arnings are enabled by default + (see --disable-warnings), 'N' can be used to reset + the list. (default: 'fE'). + --disable-warnings, --disable-pytest-warnings + disable warnings summary + -l, --showlocals show locals in tracebacks (disabled by default). + --tb=style traceback print mode + (auto/long/short/line/native/no). + --show-capture={no,stdout,stderr,log,all} + Controls how captured stdout/stderr/log is shown on + failed tests. Default is 'all'. + --full-trace don't cut any tracebacks (default is to cut). + --color=color color terminal output (yes/no/auto). + --code-highlight={yes,no} + Whether code should be highlighted (only if --color + is also enabled) + --pastebin=mode send failed|all info to bpaste.net pastebin service. + --junit-xml=path create junit-xml style report file at given path. + --junit-prefix=str prepend prefix to classnames in junit-xml output + + pytest-warnings: + -W PYTHONWARNINGS, --pythonwarnings=PYTHONWARNINGS + set which warnings to report, see -W option of + python itself. + --maxfail=num exit after first num failures or errors. + --strict-config any warnings encountered while parsing the `pytest` + section of the configuration file raise errors. + --strict-markers, --strict + markers not registered in the `markers` section of + the configuration file raise errors. + -c file load configuration from `file` instead of trying to + locate one of the implicit configuration files. + --continue-on-collection-errors + Force test execution even if collection errors + occur. + --rootdir=ROOTDIR Define root directory for tests. Can be relative + path: 'root_dir', './root_dir', + 'root_dir/another_dir/'; absolute path: + '/home/user/root_dir'; path with variables: + '$HOME/root_dir'. + + collection: + --collect-only, --co only collect tests, don't execute them. + --pyargs try to interpret all arguments as python packages. + --ignore=path ignore path during collection (multi-allowed). + --ignore-glob=path ignore path pattern during collection (multi- + allowed). + --deselect=nodeid_prefix + deselect item (via node id prefix) during collection + (multi-allowed). + --confcutdir=dir only load conftest.py's relative to specified dir. + --noconftest Don't load any conftest.py files. + --keep-duplicates Keep duplicate tests. + --collect-in-virtualenv + Don't ignore tests in a local virtualenv directory + --import-mode={prepend,append,importlib} + prepend/append to sys.path when importing test + modules and conftest files, default is to prepend. + --doctest-modules run doctests in all .py modules + --doctest-report={none,cdiff,ndiff,udiff,only_first_failure} + choose another output format for diffs on doctest + failure + --doctest-glob=pat doctests file matching pattern, default: test*.txt + --doctest-ignore-import-errors + ignore doctest ImportErrors + --doctest-continue-on-failure + for a given doctest, continue to run after the first + failure + + test session debugging and configuration: + --basetemp=dir base temporary directory for this test run.(warning: + this directory is removed if it exists) + -V, --version display pytest version and information about + plugins.When given twice, also display information + about plugins. + -h, --help show help message and configuration info + -p name early-load given plugin module name or entry point + (multi-allowed). + To avoid loading of plugins, use the `no:` prefix, + e.g. `no:doctest`. + --trace-config trace considerations of conftest.py files. + --debug store internal tracing debug information in + 'pytestdebug.log'. + -o OVERRIDE_INI, --override-ini=OVERRIDE_INI + override ini option with "option=value" style, e.g. + `-o xfail_strict=True -o cache_dir=cache`. + --assert=MODE Control assertion debugging tools. + 'plain' performs no assertion debugging. + 'rewrite' (the default) rewrites assert statements + in test modules on import to provide assert + expression information. + --setup-only only setup fixtures, do not execute tests. + --setup-show show setup of fixtures while executing tests. + --setup-plan show what fixtures and tests would be executed but + don't execute anything. + + logging: + --log-level=LEVEL level of messages to catch/display. + Not set by default, so it depends on the root/parent + log handler's effective level, where it is "WARNING" + by default. + --log-format=LOG_FORMAT + log format as used by the logging module. + --log-date-format=LOG_DATE_FORMAT + log date format as used by the logging module. + --log-cli-level=LOG_CLI_LEVEL + cli logging level. + --log-cli-format=LOG_CLI_FORMAT + log format as used by the logging module. + --log-cli-date-format=LOG_CLI_DATE_FORMAT + log date format as used by the logging module. + --log-file=LOG_FILE path to a file when logging will be written to. + --log-file-level=LOG_FILE_LEVEL + log file logging level. + --log-file-format=LOG_FILE_FORMAT + log format as used by the logging module. + --log-file-date-format=LOG_FILE_DATE_FORMAT + log date format as used by the logging module. + --log-auto-indent=LOG_AUTO_INDENT + Auto-indent multiline messages passed to the logging + module. Accepts true|on, false|off or an integer. + + [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: + + markers (linelist): markers for test functions + empty_parameter_set_mark (string): + default marker for empty parametersets + norecursedirs (args): directory patterns to avoid for recursion + testpaths (args): directories to search for tests when no files or + directories are given in the command line. + filterwarnings (linelist): + Each line specifies a pattern for + warnings.filterwarnings. Processed after + -W/--pythonwarnings. + usefixtures (args): list of default fixtures to be used with this + project + python_files (args): glob-style file patterns for Python test module + discovery + python_classes (args): + prefixes or glob names for Python test class + discovery + python_functions (args): + prefixes or glob names for Python test function and + method discovery + disable_test_id_escaping_and_forfeit_all_rights_to_community_support (bool): + disable string escape non-ascii characters, might + cause unwanted side effects(use at your own risk) + console_output_style (string): + console output: "classic", or with additional + progress information ("progress" (percentage) | + "count"). + xfail_strict (bool): default for the strict parameter of xfail markers + when not given explicitly (default: False) + enable_assertion_pass_hook (bool): + Enables the pytest_assertion_pass hook.Make sure to + delete any previously generated pyc cache files. + junit_suite_name (string): + Test suite name for JUnit report + junit_logging (string): + Write captured log messages to JUnit report: one of + no|log|system-out|system-err|out-err|all + junit_log_passing_tests (bool): + Capture log information for passing tests to JUnit + report: + junit_duration_report (string): + Duration time to report: one of total|call + junit_family (string): + Emit XML for schema: one of legacy|xunit1|xunit2 + doctest_optionflags (args): + option flags for doctests + doctest_encoding (string): + encoding used for doctest files + cache_dir (string): cache directory path. + log_level (string): default value for --log-level + log_format (string): default value for --log-format + log_date_format (string): + default value for --log-date-format + log_cli (bool): enable log display during test run (also known as + "live logging"). + log_cli_level (string): + default value for --log-cli-level + log_cli_format (string): + default value for --log-cli-format + log_cli_date_format (string): + default value for --log-cli-date-format + log_file (string): default value for --log-file + log_file_level (string): + default value for --log-file-level + log_file_format (string): + default value for --log-file-format + log_file_date_format (string): + default value for --log-file-date-format + log_auto_indent (string): + default value for --log-auto-indent + faulthandler_timeout (string): + Dump the traceback of all threads if a test takes + more than TIMEOUT seconds to finish. + addopts (args): extra command line options + minversion (string): minimally required pytest version + required_plugins (args): + plugins that must be present for pytest to run + + environment variables: + PYTEST_ADDOPTS extra command line options + PYTEST_PLUGINS comma-separated plugins to load during startup + PYTEST_DISABLE_PLUGIN_AUTOLOAD set to disable plugin auto-loading + PYTEST_DEBUG set to enable debug tracing of pytest's internals + + + to see available markers type: pytest --markers + to see available fixtures type: pytest --fixtures + (shown according to specified file_or_dir or current dir if not specified; fixtures with leading '_' are only shown with the '-v' option diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt index be22b7db872..fa37acfb447 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/requirements.txt @@ -1,4 +1,5 @@ +pallets-sphinx-themes pygments-pytest>=1.1.0 -sphinx>=1.8.2,<2.1 -sphinxcontrib-trio sphinx-removed-in>=0.2.0 +sphinx>=3.1,<4 +sphinxcontrib-trio diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/skipping.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/skipping.rst index 8d0c499b328..5c67d77a7ec 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/skipping.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/skipping.rst @@ -14,7 +14,7 @@ otherwise pytest should skip running the test altogether. Common examples are sk windows-only tests on non-windows platforms, or skipping tests that depend on an external resource which is not available at the moment (for example a database). -A **xfail** means that you expect a test to fail for some reason. +An **xfail** means that you expect a test to fail for some reason. A common example is a test for a feature not yet implemented, or a bug not yet fixed. When a test passes despite being expected to fail (marked with ``pytest.mark.xfail``), it's an **xpass** and will be reported in the test summary. @@ -145,15 +145,15 @@ You can use the ``skipif`` marker (as any other marker) on classes: .. code-block:: python @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") - class TestPosixCalls(object): + class TestPosixCalls: def test_function(self): "will not be setup or run under 'win32' platform" If the condition is ``True``, this marker will produce a skip result for each of the test methods of that class. -If you want to skip all test functions of a module, you may use -the ``pytestmark`` name on the global level: +If you want to skip all test functions of a module, you may use the +:globalvar:`pytestmark` global: .. code-block:: python @@ -179,14 +179,17 @@ information. Skipping on a missing import dependency ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can use the following helper at module level -or within a test or test setup function:: +You can skip tests on a missing import by using :ref:`pytest.importorskip ref` +at module level, within a test, or test setup function. + +.. code-block:: python docutils = pytest.importorskip("docutils") -If ``docutils`` cannot be imported here, this will lead to a -skip outcome of the test. You can also skip based on the -version number of a library:: +If ``docutils`` cannot be imported here, this will lead to a skip outcome of +the test. You can also skip based on the version number of a library: + +.. code-block:: python docutils = pytest.importorskip("docutils", minversion="0.3") @@ -223,17 +226,19 @@ XFail: mark test functions as expected to fail ---------------------------------------------- You can use the ``xfail`` marker to indicate that you -expect a test to fail:: +expect a test to fail: + +.. code-block:: python @pytest.mark.xfail def test_function(): ... -This test will be run but no traceback will be reported -when it fails. Instead terminal reporting will list it in the -"expected to fail" (``XFAIL``) or "unexpectedly passing" (``XPASS``) sections. +This test will run but no traceback will be reported when it fails. Instead, terminal +reporting will list it in the "expected to fail" (``XFAIL``) or "unexpectedly +passing" (``XPASS``) sections. -Alternatively, you can also mark a test as ``XFAIL`` from within a test or setup function +Alternatively, you can also mark a test as ``XFAIL`` from within the test or its setup function imperatively: .. code-block:: python @@ -242,50 +247,47 @@ imperatively: if not valid_config(): pytest.xfail("failing configuration (but should work)") -This will unconditionally make ``test_function`` ``XFAIL``. Note that no other code is executed -after ``pytest.xfail`` call, differently from the marker. That's because it is implemented -internally by raising a known exception. +.. code-block:: python -**Reference**: :ref:`pytest.mark.xfail ref` + def test_function2(): + import slow_module + if slow_module.slow_function(): + pytest.xfail("slow_module taking too long") -.. _`xfail strict tutorial`: +These two examples illustrate situations where you don't want to check for a condition +at the module level, which is when a condition would otherwise be evaluated for marks. -``strict`` parameter -~~~~~~~~~~~~~~~~~~~~ +This will make ``test_function`` ``XFAIL``. Note that no other code is executed after +the ``pytest.xfail`` call, differently from the marker. That's because it is implemented +internally by raising a known exception. + +**Reference**: :ref:`pytest.mark.xfail ref` +``condition`` parameter +~~~~~~~~~~~~~~~~~~~~~~~ -Both ``XFAIL`` and ``XPASS`` don't fail the test suite, unless the ``strict`` keyword-only -parameter is passed as ``True``: +If a test is only expected to fail under a certain condition, you can pass +that condition as the first parameter: .. code-block:: python - @pytest.mark.xfail(strict=True) + @pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library") def test_function(): ... - -This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite. - -You can change the default value of the ``strict`` parameter using the -``xfail_strict`` ini option: - -.. code-block:: ini - - [pytest] - xfail_strict=true - +Note that you have to pass a reason as well (see the parameter description at +:ref:`pytest.mark.xfail ref`). ``reason`` parameter ~~~~~~~~~~~~~~~~~~~~ -As with skipif_ you can also mark your expectation of a failure -on a particular platform: +You can specify the motive of an expected failure with the ``reason`` parameter: .. code-block:: python - @pytest.mark.xfail(sys.version_info >= (3, 6), reason="python3.6 api changes") + @pytest.mark.xfail(reason="known parser issue") def test_function(): ... @@ -320,6 +322,31 @@ even executed, use the ``run`` parameter as ``False``: This is specially useful for xfailing tests that are crashing the interpreter and should be investigated later. +.. _`xfail strict tutorial`: + +``strict`` parameter +~~~~~~~~~~~~~~~~~~~~ + +Both ``XFAIL`` and ``XPASS`` don't fail the test suite by default. +You can change this by setting the ``strict`` keyword-only parameter to ``True``: + +.. code-block:: python + + @pytest.mark.xfail(strict=True) + def test_function(): + ... + + +This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite. + +You can change the default value of the ``strict`` parameter using the +``xfail_strict`` ini option: + +.. code-block:: ini + + [pytest] + xfail_strict=true + Ignoring xfail ~~~~~~~~~~~~~~ @@ -346,7 +373,7 @@ Running it with the report-on-xfail option gives this output: example $ pytest -rx xfail_demo.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR/example collected 7 items @@ -366,7 +393,7 @@ Running it with the report-on-xfail option gives this output: XFAIL xfail_demo.py::test_hello6 reason: reason XFAIL xfail_demo.py::test_hello7 - ======================== 7 xfailed in 0.12 seconds ========================= + ============================ 7 xfailed in 0.12s ============================ .. _`skip/xfail with parametrize`: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/sponsor.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/sponsor.rst new file mode 100644 index 00000000000..8362a7f0a3a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/sponsor.rst @@ -0,0 +1,26 @@ +Sponsor +======= + +pytest is maintained by a team of volunteers from all around the world in their free time. While +we work on pytest because we love the project and use it daily at our daily jobs, monetary +compensation when possible is welcome to justify time away from friends, family and personal time. + +Money is also used to fund local sprints, merchandising (stickers to distribute in conferences for example) +and every few years a large sprint involving all members. + +OpenCollective +-------------- + +`Open Collective`_ is an online funding platform for open and transparent communities. +It provide tools to raise money and share your finances in full transparency. + +It is the platform of choice for individuals and companies that want to make one-time or +monthly donations directly to the project. + +See more details in the `pytest collective`_. + + +.. _Tidelift: https://tidelift.com +.. _Tidelift subscription: https://tidelift.com/subscription/pkg/pypi-pytest +.. _Open Collective: https://opencollective.com +.. _pytest collective: https://opencollective.com/pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst index be0c6fb9fd4..216ccb8dd8a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/talks.rst @@ -2,15 +2,6 @@ Talks and Tutorials ========================== -.. - .. sidebar:: Next Open Trainings - - `Professional Testing with Python - <http://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, - 26-28 April 2017, Leipzig, Germany. - -.. _`funcargs`: funcargs.html - Books --------------------------------------------- @@ -23,6 +14,18 @@ Books Talks and blog postings --------------------------------------------- +- Webinar: `pytest: Test Driven Development für Python (German) <https://bruhin.software/ins-pytest/>`_, Florian Bruhin, via mylearning.ch, 2020 + +- Webinar: `Simplify Your Tests with Fixtures <https://blog.jetbrains.com/pycharm/2020/08/webinar-recording-simplify-your-tests-with-fixtures-with-oliver-bestwalter/>`_, Oliver Bestwalter, via JetBrains, 2020 + +- Training: `Introduction to pytest - simple, rapid and fun testing with Python <https://www.youtube.com/watch?v=CMuSn9cofbI>`_, Florian Bruhin, PyConDE 2019 + +- Abridged metaprogramming classics - this episode: pytest, Oliver Bestwalter, PyConDE 2019 (`repository <https://github.com/obestwalter/abridged-meta-programming-classics>`__, `recording <https://www.youtube.com/watch?v=zHpeMTJsBRk&feature=youtu.be>`__) + +- Testing PySide/PyQt code easily using the pytest framework, Florian Bruhin, Qt World Summit 2019 (`slides <https://bruhin.software/talks/qtws19.pdf>`__, `recording <https://www.youtube.com/watch?v=zdsBS5BXGqQ>`__) + +- `pytest: recommendations, basic packages for testing in Python and Django, Andreu Vallbona, PyBCN June 2019 <https://www.slideshare.net/AndreuVallbonaPlazas/pybcn-pytest-recomendaciones-paquetes-bsicos-para-testing-en-python-y-django>`_. + - pytest: recommendations, basic packages for testing in Python and Django, Andreu Vallbona, PyconES 2017 (`slides in english <http://talks.apsl.io/testing-pycones-2017/>`_, `video in spanish <https://www.youtube.com/watch?v=K20GeR-lXDk>`_) - `pytest advanced, Andrew Svetlov (Russian, PyCon Russia, 2016) @@ -47,13 +50,11 @@ Talks and blog postings <https://www.youtube.com/watch?v=P-AhpukDIik>`_ - `3-part blog series about pytest from @pydanny alias Daniel Greenfeld (January - 2014) <http://pydanny.com/pytest-no-boilerplate-testing.html>`_ + 2014) <https://daniel.roygreenfeld.com/pytest-no-boilerplate-testing.html>`_ - `pytest: helps you write better Django apps, Andreas Pelme, DjangoCon Europe 2014 <https://www.youtube.com/watch?v=aaArYVh6XSM>`_. -- :ref:`fixtures` - - `Testing Django Applications with pytest, Andreas Pelme, EuroPython 2013 <https://www.youtube.com/watch?v=aUf8Fkb7TaY>`_. @@ -69,7 +70,7 @@ Talks and blog postings - `pytest introduction from Brian Okken (January 2013) <http://pythontesting.net/framework/pytest-introduction/>`_ -- pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_) +- pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <https://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_) - `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_ - `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tidelift.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tidelift.rst index 728bf221905..8ce55e97b38 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tidelift.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tidelift.rst @@ -1,4 +1,45 @@ +pytest for enterprise +===================== +`Tidelift`_ is working with the maintainers of pytest and thousands of other +open source projects to deliver commercial support and maintenance for the open source dependencies you use +to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the +exact dependencies you use. +`Get more details <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise>`_ -.. include:: ../../TIDELIFT.rst +The Tidelift Subscription is a managed open source subscription for application dependencies covering millions of open source projects across JavaScript, Python, Java, PHP, Ruby, .NET, and more. + +Your subscription includes: + +* **Security updates** + + - Tidelift's security response team coordinates patches for new breaking security vulnerabilities and alerts immediately through a private channel, so your software supply chain is always secure. + +* **Licensing verification and indemnification** + + - Tidelift verifies license information to enable easy policy enforcement and adds intellectual property indemnification to cover creators and users in case something goes wrong. You always have a 100% up-to-date bill of materials for your dependencies to share with your legal team, customers, or partners. + +* **Maintenance and code improvement** + + - Tidelift ensures the software you rely on keeps working as long as you need it to work. Your managed dependencies are actively maintained and we recruit additional maintainers where required. + +* **Package selection and version guidance** + + - Tidelift helps you choose the best open source packages from the start—and then guide you through updates to stay on the best releases as new issues arise. + +* **Roadmap input** + + - Take a seat at the table with the creators behind the software you use. Tidelift's participating maintainers earn more income as their software is used by more subscribers, so they're interested in knowing what you need. + +* **Tooling and cloud integration** + + - Tidelift works with GitHub, GitLab, BitBucket, and every cloud platform (and other deployment targets, too). + +The end result? All of the capabilities you expect from commercial-grade software, for the full breadth of open +source you use. That means less time grappling with esoteric open source trivia, and more time building your own +applications—and your business. + +`Request a demo <https://tidelift.com/subscription/request-a-demo?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise>`_ + +.. _Tidelift: https://tidelift.com diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tmpdir.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tmpdir.rst index 330011bb348..5f882b1400f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tmpdir.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/tmpdir.rst @@ -22,7 +22,7 @@ created in the `base temporary directory`_. # content of test_tmp_path.py import os - CONTENT = u"content" + CONTENT = "content" def test_create_file(tmp_path): @@ -41,7 +41,7 @@ Running this would result in a passed test except for the last $ pytest test_tmp_path.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -64,7 +64,9 @@ Running this would result in a passed test except for the last E assert 0 test_tmp_path.py:13: AssertionError - ========================= 1 failed in 0.12 seconds ========================= + ========================= short test summary info ========================== + FAILED test_tmp_path.py::test_create_file - assert 0 + ============================ 1 failed in 0.12s ============================= .. _`tmp_path_factory example`: @@ -90,10 +92,14 @@ provide a temporary directory unique to the test invocation, created in the `base temporary directory`_. ``tmpdir`` is a `py.path.local`_ object which offers ``os.path`` methods -and more. Here is an example test usage:: +and more. Here is an example test usage: + +.. code-block:: python # content of test_tmpdir.py import os + + def test_create_file(tmpdir): p = tmpdir.mkdir("sub").join("hello.txt") p.write("content") @@ -108,7 +114,7 @@ Running this would result in a passed test except for the last $ pytest test_tmpdir.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -128,8 +134,10 @@ Running this would result in a passed test except for the last > assert 0 E assert 0 - test_tmpdir.py:7: AssertionError - ========================= 1 failed in 0.12 seconds ========================= + test_tmpdir.py:9: AssertionError + ========================= short test summary info ========================== + FAILED test_tmpdir.py::test_create_file - assert 0 + ============================ 1 failed in 0.12s ============================= .. _`tmpdir factory example`: @@ -184,8 +192,13 @@ You can override the default temporary directory setting like this: pytest --basetemp=mydir -When distributing tests on the local machine, ``pytest`` takes care to -configure a basetemp directory for the sub processes such that all temporary +.. warning:: + + The contents of ``mydir`` will be completely removed, so make sure to use a directory + for that purpose only. + +When distributing tests on the local machine using ``pytest-xdist``, care is taken to +automatically configure a basetemp directory for the sub processes such that all temporary data lands below a single per-test run basetemp directory. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/unittest.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/unittest.rst index 05632aef4b2..130e7705b8d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/unittest.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/unittest.rst @@ -10,7 +10,9 @@ It's meant for leveraging existing ``unittest``-based test suites to use pytest as a test runner and also allow to incrementally adapt the test suite to take full advantage of pytest's features. -To run an existing ``unittest``-style test suite using ``pytest``, type:: +To run an existing ``unittest``-style test suite using ``pytest``, type: + +.. code-block:: bash pytest tests @@ -78,7 +80,9 @@ Running your unittest with ``pytest`` allows you to use its tests. Assuming you have at least skimmed the pytest fixture features, let's jump-start into an example that integrates a pytest ``db_class`` fixture, setting up a class-cached database object, and then reference -it from a unittest-style test:: +it from a unittest-style test: + +.. code-block:: python # content of conftest.py @@ -87,10 +91,12 @@ it from a unittest-style test:: import pytest + @pytest.fixture(scope="class") def db_class(request): - class DummyDB(object): + class DummyDB: pass + # set a class attribute on the invoking test context request.cls.db = DummyDB() @@ -103,21 +109,24 @@ as the ``cls`` attribute, denoting the class from which the fixture is used. This architecture de-couples fixture writing from actual test code and allows re-use of the fixture by a minimal reference, the fixture name. So let's write an actual ``unittest.TestCase`` class using our -fixture definition:: +fixture definition: + +.. code-block:: python # content of test_unittest_db.py import unittest import pytest + @pytest.mark.usefixtures("db_class") class MyTest(unittest.TestCase): def test_method1(self): assert hasattr(self, "db") - assert 0, self.db # fail for demo purposes + assert 0, self.db # fail for demo purposes def test_method2(self): - assert 0, self.db # fail for demo purposes + assert 0, self.db # fail for demo purposes The ``@pytest.mark.usefixtures("db_class")`` class-decorator makes sure that the pytest fixture function ``db_class`` is called once per class. @@ -128,7 +137,7 @@ the ``self.db`` values in the traceback: $ pytest test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 2 items @@ -142,22 +151,25 @@ the ``self.db`` values in the traceback: def test_method1(self): assert hasattr(self, "db") - > assert 0, self.db # fail for demo purposes + > assert 0, self.db # fail for demo purposes E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef> E assert 0 - test_unittest_db.py:9: AssertionError + test_unittest_db.py:10: AssertionError ___________________________ MyTest.test_method2 ____________________________ self = <test_unittest_db.MyTest testMethod=test_method2> def test_method2(self): - > assert 0, self.db # fail for demo purposes + > assert 0, self.db # fail for demo purposes E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef> E assert 0 - test_unittest_db.py:12: AssertionError - ========================= 2 failed in 0.12 seconds ========================= + test_unittest_db.py:13: AssertionError + ========================= short test summary info ========================== + FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft... + FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft... + ============================ 2 failed in 0.12s ============================= This default pytest traceback shows that the two test methods share the same ``self.db`` instance which was our intention @@ -179,17 +191,19 @@ Let's look at an ``initdir`` fixture which makes all test methods of a ``TestCase`` class execute in a temporary directory with a pre-initialized ``samplefile.ini``. Our ``initdir`` fixture itself uses the pytest builtin :ref:`tmpdir <tmpdir>` fixture to delegate the -creation of a per-test temporary directory:: +creation of a per-test temporary directory: + +.. code-block:: python # content of test_unittest_cleandir.py import pytest import unittest - class MyTest(unittest.TestCase): + class MyTest(unittest.TestCase): @pytest.fixture(autouse=True) def initdir(self, tmpdir): - tmpdir.chdir() # change to pytest-provided temporary directory + tmpdir.chdir() # change to pytest-provided temporary directory tmpdir.join("samplefile.ini").write("# testdata") def test_method(self): @@ -208,7 +222,7 @@ Running this test module ...: $ pytest -q test_unittest_cleandir.py . [100%] - 1 passed in 0.12 seconds + 1 passed in 0.12s ... gives us one passed test because the ``initdir`` fixture function was executed ahead of the ``test_method``. @@ -229,17 +243,6 @@ was executed ahead of the ``test_method``. .. note:: - Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will - disable tearDown and cleanup methods for the case that an Exception - occurs. This allows proper post mortem debugging for all applications - which have significant logic in their tearDown machinery. However, - supporting this feature has the following side effect: If people - overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to - to overwrite ``debug`` in the same way (this is also true for standard - unittest). - -.. note:: - Due to architectural differences between the two frameworks, setup and teardown for ``unittest``-based tests is performed during the ``call`` phase of testing instead of in ``pytest``'s standard ``setup`` and ``teardown`` diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/usage.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/usage.rst index acf736f211e..3c03db4540f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/usage.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/usage.rst @@ -33,6 +33,20 @@ Running ``pytest`` can result in six different exit codes: :Exit code 4: pytest command line usage error :Exit code 5: No tests were collected +They are represented by the :class:`pytest.ExitCode` enum. The exit codes being a part of the public API can be imported and accessed directly using: + +.. code-block:: python + + from pytest import ExitCode + +.. note:: + + If you would like to customize the exit code in some scenarios, specially when + no tests are collected, consider using the + `pytest-custom_exit_code <https://github.com/yashtodi94/pytest-custom_exit_code>`__ + plugin. + + Getting help on version, option names, environment variables -------------------------------------------------------------- @@ -43,6 +57,8 @@ Getting help on version, option names, environment variables pytest -h | --help # show help on command line and config file options +The full command-line flags can be found in the :ref:`reference <command-line-flags>`. + .. _maxfail: Stopping after the first (or N) failures @@ -52,8 +68,8 @@ To stop the testing process after the first (N) failures: .. code-block:: bash - pytest -x # stop after first failure - pytest --maxfail=2 # stop after two failures + pytest -x # stop after first failure + pytest --maxfail=2 # stop after two failures .. _select-tests: @@ -80,8 +96,8 @@ Pytest supports several ways to run and select tests from the command-line. pytest -k "MyClass and not method" -This will run tests which contain names that match the given *string expression*, which can -include Python operators that use filenames, class names and function names as variables. +This will run tests which contain names that match the given *string expression* (case-insensitive), +which can include Python operators that use filenames, class names and function names as variables. The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``. .. _nodeids: @@ -155,11 +171,11 @@ option you make sure a trace is shown. Detailed summary report ----------------------- - - The ``-r`` flag can be used to display a "short test summary info" at the end of the test session, making it easy in large test suites to get a clear picture of all failures, skips, xfails, etc. +It defaults to ``fE`` to list failures and errors. + Example: .. code-block:: python @@ -202,7 +218,7 @@ Example: $ pytest -ra =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 6 items @@ -227,13 +243,13 @@ Example: test_example.py:14: AssertionError ========================= short test summary info ========================== - SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test + SKIPPED [1] test_example.py:22: skipping this test XFAIL test_example.py::test_xfail reason: xfailing this test XPASS test_example.py::test_xpass always xfail ERROR test_example.py::test_error - assert 0 FAILED test_example.py::test_fail - assert 0 - = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = + == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s === The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes". @@ -247,8 +263,12 @@ Here is the full list of available characters that can be used: - ``X`` - xpassed - ``p`` - passed - ``P`` - passed with output + +Special characters for (de)selection of groups: + - ``a`` - all except ``pP`` - ``A`` - all + - ``N`` - none, this can be used to display nothing (since ``fE`` is the default) More than one character can be used, so for example to only see failed and skipped tests, you can execute: @@ -256,7 +276,7 @@ More than one character can be used, so for example to only see failed and skipp $ pytest -rfs =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 6 items @@ -282,8 +302,8 @@ More than one character can be used, so for example to only see failed and skipp test_example.py:14: AssertionError ========================= short test summary info ========================== FAILED test_example.py::test_fail - assert 0 - SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test - = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = + SKIPPED [1] test_example.py:22: skipping this test + == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s === Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had captured output: @@ -292,7 +312,7 @@ captured output: $ pytest -rpP =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 6 items @@ -322,7 +342,7 @@ captured output: ok ========================= short test summary info ========================== PASSED test_example.py::test_ok - = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = + == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s === .. _pdb-option: @@ -408,15 +428,47 @@ Pytest supports the use of ``breakpoint()`` with the following behaviours: Profiling test execution duration ------------------------------------- -.. versionadded: 2.2 +.. versionchanged:: 6.0 -To get a list of the slowest 10 test durations: +To get a list of the slowest 10 test durations over 1.0s long: .. code-block:: bash - pytest --durations=10 + pytest --durations=10 --durations-min=1.0 + +By default, pytest will not show test durations that are too small (<0.005s) unless ``-vv`` is passed on the command-line. + + +.. _faulthandler: + +Fault Handler +------------- + +.. versionadded:: 5.0 + +The `faulthandler <https://docs.python.org/3/library/faulthandler.html>`__ standard module +can be used to dump Python tracebacks on a segfault or after a timeout. + +The module is automatically enabled for pytest runs, unless the ``-p no:faulthandler`` is given +on the command-line. + +Also the :confval:`faulthandler_timeout=X<faulthandler_timeout>` configuration option can be used +to dump the traceback of all threads if a test takes longer than ``X`` +seconds to finish (not available on Windows). + +.. note:: + + This functionality has been integrated from the external + `pytest-faulthandler <https://github.com/pytest-dev/pytest-faulthandler>`__ plugin, with two + small differences: + + * To disable it, use ``-p no:faulthandler`` instead of ``--no-faulthandler``: the former + can be used with any plugin, so it saves one option. + + * The ``--faulthandler-timeout`` command-line option has become the + :confval:`faulthandler_timeout` configuration option. It can still be configured from + the command-line using ``-o faulthandler_timeout=X``. -By default, pytest will not show test durations that are too small (<0.01s) unless ``-vv`` is passed on the command-line. Creating JUnitXML format files ---------------------------------------------------- @@ -607,7 +659,7 @@ to all tests. record_testsuite_property("STORAGE_TYPE", "CEPH") - class TestMe(object): + class TestMe: def test_foo(self): assert True @@ -634,12 +686,6 @@ Creating resultlog format files ---------------------------------------------------- - - This option is rarely used and is scheduled for removal in 5.0. - - See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__ - for more information. - To create plain-text machine-readable result files you can issue: .. code-block:: bash @@ -649,6 +695,16 @@ To create plain-text machine-readable result files you can issue: and look at the content at the ``path`` location. Such files are used e.g. by the `PyPy-test`_ web page to show test results over several revisions. +.. warning:: + + This option is rarely used and is scheduled for removal in pytest 6.0. + + If you use this option, consider using the new `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin instead. + + See `the deprecation docs <https://docs.pytest.org/en/stable/deprecations.html#result-log-result-log>`__ + for more information. + + .. _`PyPy-test`: http://buildbot.pypy.org/summary @@ -673,6 +729,11 @@ for example ``-x`` if you only want to send one particular failure. Currently only pasting to the http://bpaste.net service is implemented. +.. versionchanged:: 5.2 + +If creating the URL fails for any reason, a warning is generated instead of failing the +entire test suite. + Early loading plugins --------------------- @@ -709,24 +770,33 @@ Calling pytest from Python code -You can invoke ``pytest`` from Python code directly:: +You can invoke ``pytest`` from Python code directly: + +.. code-block:: python pytest.main() this acts as if you would call "pytest" from the command line. It will not raise ``SystemExit`` but return the exitcode instead. -You can pass in options and arguments:: +You can pass in options and arguments: + +.. code-block:: python - pytest.main(['-x', 'mytestdir']) + pytest.main(["-x", "mytestdir"]) -You can specify additional plugins to ``pytest.main``:: +You can specify additional plugins to ``pytest.main``: + +.. code-block:: python # content of myinvoke.py import pytest - class MyPlugin(object): + + + class MyPlugin: def pytest_sessionfinish(self): print("*** test run reporting finishing") + pytest.main(["-qq"], plugins=[MyPlugin()]) Running it will show that ``MyPlugin`` was added and its @@ -754,6 +824,9 @@ hook was invoked: E assert 0 test_example.py:14: AssertionError + ========================= short test summary info ========================== + FAILED test_example.py::test_fail - assert 0 + ERROR test_example.py::test_error - assert 0 .. note:: @@ -764,5 +837,4 @@ hook was invoked: multiple calls to ``pytest.main()`` from the same process (in order to re-run tests, for example) is not recommended. - -.. include:: links.inc +.. _jenkins: http://jenkins-ci.org/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/warnings.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/warnings.rst index 8f8d6f3e1c6..7232b676d24 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/warnings.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/warnings.rst @@ -28,7 +28,7 @@ Running pytest now produces this output: $ pytest test_show_warnings.py =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache rootdir: $REGENDOC_TMPDIR collected 1 item @@ -40,8 +40,8 @@ Running pytest now produces this output: $REGENDOC_TMPDIR/test_show_warnings.py:5: UserWarning: api v1, should use functions from v2 warnings.warn(UserWarning("api v1, should use functions from v2")) - -- Docs: https://docs.pytest.org/en/latest/warnings.html - =================== 1 passed, 1 warnings in 0.12 seconds =================== + -- Docs: https://docs.pytest.org/en/stable/warnings.html + ======================= 1 passed, 1 warning in 0.12s ======================= The ``-W`` flag can be passed to control which warnings will be displayed or even turn them into errors: @@ -64,18 +64,34 @@ them into errors: E UserWarning: api v1, should use functions from v2 test_show_warnings.py:5: UserWarning - 1 failed in 0.12 seconds + ========================= short test summary info ========================== + FAILED test_show_warnings.py::test_one - UserWarning: api v1, should use ... + 1 failed in 0.12s -The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option. -For example, the configuration below will ignore all user warnings, but will transform +The same option can be set in the ``pytest.ini`` or ``pyproject.toml`` file using the +``filterwarnings`` ini option. For example, the configuration below will ignore all +user warnings and specific deprecation warnings matching a regex, but will transform all other warnings into errors. .. code-block:: ini + # pytest.ini [pytest] filterwarnings = error ignore::UserWarning + ignore:function ham\(\) is deprecated:DeprecationWarning + +.. code-block:: toml + + # pyproject.toml + [tool.pytest.ini_options] + filterwarnings = [ + "error", + "ignore::UserWarning", + # note the use of single quote below to denote "raw" strings in TOML + 'ignore:function ham\(\) is deprecated:DeprecationWarning', + ] When a warning matches more than one option in the list, the action for the last matching option @@ -115,7 +131,7 @@ Filters applied using a mark take precedence over filters passed on the command by the ``filterwarnings`` ini option. You may apply a filter to all tests of a class by using the ``filterwarnings`` mark as a class -decorator or to all tests in a module by setting the ``pytestmark`` variable: +decorator or to all tests in a module by setting the :globalvar:`pytestmark` variable: .. code-block:: python @@ -127,7 +143,7 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable: *Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_ *plugin.* -.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W +.. _`-W option`: https://docs.python.org/3/using/cmdline.html#cmdoption-w .. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter .. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings @@ -180,6 +196,7 @@ This will ignore all warnings of type ``DeprecationWarning`` where the start of the regular expression ``".*U.*mode is deprecated"``. .. note:: + If warnings are configured at the interpreter level, using the `PYTHONWARNINGS <https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>`_ environment variable or the ``-W`` command-line option, pytest will not configure any filters by default. @@ -197,7 +214,7 @@ the regular expression ``".*U.*mode is deprecated"``. Ensuring code triggers a deprecation warning -------------------------------------------- -You can also call a global helper for checking +You can also use :func:`pytest.deprecated_call` for checking that a certain function call triggers a ``DeprecationWarning`` or ``PendingDeprecationWarning``: @@ -206,13 +223,18 @@ that a certain function call triggers a ``DeprecationWarning`` or import pytest - def test_global(): - pytest.deprecated_call(myfunction, 17) + def test_myfunction_deprecated(): + with pytest.deprecated_call(): + myfunction(17) + +This test will fail if ``myfunction`` does not issue a deprecation warning +when called with a ``17`` argument. By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be -caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide -them. If you wish to record them in your own code, use the -command ``warnings.simplefilter('always')``: +caught when using :func:`pytest.warns` or :ref:`recwarn <recwarn>` because +the default Python warnings filters hide +them. If you wish to record them in your own code, use +``warnings.simplefilter('always')``: .. code-block:: python @@ -222,19 +244,13 @@ command ``warnings.simplefilter('always')``: def test_deprecation(recwarn): warnings.simplefilter("always") - warnings.warn("deprecated", DeprecationWarning) + myfunction(17) assert len(recwarn) == 1 assert recwarn.pop(DeprecationWarning) -You can also use it as a contextmanager: - -.. code-block:: python - - def test_global(): - with pytest.deprecated_call(): - myobject.deprecated_method() - +The :ref:`recwarn <recwarn>` fixture automatically ensures to reset the warnings +filter at the end of the test, so no global state is leaked. .. _`asserting warnings`: @@ -277,7 +293,9 @@ argument ``match`` to assert that the exception matches a text or regex:: ... Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted... -You can also call ``pytest.warns`` on a function or code string:: +You can also call ``pytest.warns`` on a function or code string: + +.. code-block:: python pytest.warns(expected_warning, func, *args, **kwargs) pytest.warns(expected_warning, "func(*args, **kwargs)") @@ -349,7 +367,7 @@ warnings, or index into it to get a particular recorded warning. .. currentmodule:: _pytest.warnings -Full API: :class:`WarningsRecorder`. +Full API: :class:`~_pytest.recwarn.WarningsRecorder`. .. _custom_failure_messages: @@ -377,8 +395,6 @@ custom error message. Internal pytest warnings ------------------------ - - pytest may generate its own warnings in some situations, such as improper usage or deprecated features. For example, pytest will emit a warning if it encounters a class that matches :confval:`python_classes` but also @@ -403,32 +419,12 @@ defines an ``__init__`` constructor, as this prevents the class from being insta $REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor (from: test_pytest_warnings.py) class Test: - -- Docs: https://docs.pytest.org/en/latest/warnings.html - 1 warnings in 0.12 seconds + -- Docs: https://docs.pytest.org/en/stable/warnings.html + 1 warning in 0.12s These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings. Please read our :ref:`backwards-compatibility` to learn how we proceed about deprecating and eventually removing features. -The following warning types ares used by pytest and are part of the public API: - -.. autoclass:: pytest.PytestWarning - -.. autoclass:: pytest.PytestAssertRewriteWarning - -.. autoclass:: pytest.PytestCacheWarning - -.. autoclass:: pytest.PytestCollectionWarning - -.. autoclass:: pytest.PytestConfigWarning - -.. autoclass:: pytest.PytestDeprecationWarning - -.. autoclass:: pytest.PytestExperimentalApiWarning - -.. autoclass:: pytest.PytestUnhandledCoroutineWarning - -.. autoclass:: pytest.PytestUnknownMarkWarning - -.. autoclass:: pytest.RemovedInPytest4Warning +The full list of warnings is listed in :ref:`the reference documentation <warnings ref>`. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/writing_plugins.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/writing_plugins.rst index 04dc68b642d..0492b5fcf7e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/writing_plugins.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/writing_plugins.rst @@ -33,26 +33,34 @@ Plugin discovery order at tool startup ``pytest`` loads plugin modules at tool startup in the following way: -* by loading all builtin plugins +1. by scanning the command line for the ``-p no:name`` option + and *blocking* that plugin from being loaded (even builtin plugins can + be blocked this way). This happens before normal command-line parsing. -* by loading all plugins registered through `setuptools entry points`_. +2. by loading all builtin plugins. -* by pre-scanning the command line for the ``-p name`` option - and loading the specified plugin before actual command line parsing. +3. by scanning the command line for the ``-p name`` option + and loading the specified plugin. This happens before normal command-line parsing. -* by loading all :file:`conftest.py` files as inferred by the command line - invocation: +4. by loading all plugins registered through `setuptools entry points`_. - - if no test paths are specified use current dir as a test path - - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative - to the directory part of the first test path. +5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable. - Note that pytest does not find ``conftest.py`` files in deeper nested - sub directories at tool startup. It is usually a good idea to keep - your ``conftest.py`` file in the top level test or project root directory. +6. by loading all :file:`conftest.py` files as inferred by the command line + invocation: -* by recursively loading all plugins specified by the - ``pytest_plugins`` variable in ``conftest.py`` files + - if no test paths are specified, use the current dir as a test path + - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative + to the directory part of the first test path. After the ``conftest.py`` + file is loaded, load all plugins specified in its + :globalvar:`pytest_plugins` variable if present. + + Note that pytest does not find ``conftest.py`` files in deeper nested + sub directories at tool startup. It is usually a good idea to keep + your ``conftest.py`` file in the top level test or project root directory. + +7. by recursively loading all plugins specified by the + :globalvar:`pytest_plugins` variable in ``conftest.py`` files. .. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/ @@ -164,7 +172,7 @@ If a package is installed this way, ``pytest`` will load .. note:: Make sure to include ``Framework :: Pytest`` in your list of - `PyPI classifiers <https://python-packaging-user-guide.readthedocs.io/distributing/#classifiers>`_ + `PyPI classifiers <https://pypi.org/classifiers/>`_ to make it easy for users to find your plugin. @@ -179,11 +187,12 @@ assertion failures. This is provided by "assertion rewriting" which modifies the parsed AST before it gets compiled to bytecode. This is done via a :pep:`302` import hook which gets installed early on when ``pytest`` starts up and will perform this rewriting when modules get -imported. However since we do not want to test different bytecode -then you will run in production this hook only rewrites test modules -themselves as well as any modules which are part of plugins. Any -other imported module will not be rewritten and normal assertion -behaviour will happen. +imported. However, since we do not want to test different bytecode +from what you will run in production, this hook only rewrites test modules +themselves (as defined by the :confval:`python_files` configuration option), +and any modules which are part of plugins. +Any other imported module will not be rewritten and normal assertion behaviour +will happen. If you have assertion helpers in other modules where you would need assertion rewriting to be enabled you need to ask ``pytest`` @@ -226,7 +235,7 @@ import ``helper.py`` normally. The contents of Requiring/Loading plugins in a test module or conftest file ----------------------------------------------------------- -You can require plugins in a test module or a ``conftest.py`` file like this: +You can require plugins in a test module or a ``conftest.py`` file using :globalvar:`pytest_plugins`: .. code-block:: python @@ -240,31 +249,31 @@ application modules: pytest_plugins = "myapp.testsupport.myplugin" -``pytest_plugins`` variables are processed recursively, so note that in the example above -if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents +:globalvar:`pytest_plugins` are processed recursively, so note that in the example above +if ``myapp.testsupport.myplugin`` also declares :globalvar:`pytest_plugins`, the contents of the variable will also be loaded as plugins, and so on. .. _`requiring plugins in non-root conftests`: .. note:: - Requiring plugins using a ``pytest_plugins`` variable in non-root + Requiring plugins using :globalvar:`pytest_plugins` variable in non-root ``conftest.py`` files is deprecated. This is important because ``conftest.py`` files implement per-directory hook implementations, but once a plugin is imported, it will affect the entire directory tree. In order to avoid confusion, defining - ``pytest_plugins`` in any ``conftest.py`` file which is not located in the + :globalvar:`pytest_plugins` in any ``conftest.py`` file which is not located in the tests root directory is deprecated, and will raise a warning. This mechanism makes it easy to share fixtures within applications or even external applications without the need to create external plugins using the ``setuptools``'s entry point technique. -Plugins imported by ``pytest_plugins`` will also automatically be marked +Plugins imported by :globalvar:`pytest_plugins` will also automatically be marked for assertion rewriting (see :func:`pytest.register_assert_rewrite`). However for this to have any effect the module must not be imported already; if it was already imported at the time the -``pytest_plugins`` statement is processed, a warning will result and +:globalvar:`pytest_plugins` statement is processed, a warning will result and assertions inside the plugin will not be rewritten. To fix this you can either call :func:`pytest.register_assert_rewrite` yourself before the module is imported, or you can arrange the code to delay the @@ -335,8 +344,6 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello .. code-block:: python - # -*- coding: utf-8 -*- - import pytest @@ -405,7 +412,7 @@ return a result object, with which we can assert the tests' outcomes. result.assert_outcomes(passed=4) -additionally it is possible to copy examples for an example folder before running pytest on it +Additionally it is possible to copy examples for an example folder before running pytest on it. .. code-block:: ini @@ -431,9 +438,9 @@ additionally it is possible to copy examples for an example folder before runnin $ pytest =========================== test session starts ============================ - platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y + platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y cachedir: $PYTHON_PREFIX/.pytest_cache - rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + rootdir: $REGENDOC_TMPDIR, configfile: pytest.ini collected 2 items test_example.py .. [100%] @@ -443,8 +450,8 @@ additionally it is possible to copy examples for an example folder before runnin $REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time testdir.copy_example("test_example.py") - -- Docs: https://docs.pytest.org/en/latest/warnings.html - =================== 2 passed, 1 warnings in 0.12 seconds =================== + -- Docs: https://docs.pytest.org/en/stable/warnings.html + ======================= 2 passed, 1 warning in 0.12s ======================= For more information about the result object that ``runpytest()`` returns, and the methods that it provides please check out the :py:class:`RunResult @@ -509,6 +516,7 @@ call only executes until the first of N registered functions returns a non-None result which is then taken as result of the overall hook call. The remaining hook functions will not be called in this case. +.. _`hookwrapper`: hookwrapper: executing around other hooks ------------------------------------------------- @@ -553,8 +561,10 @@ perform tracing or other side effects around the actual hook implementations. If the result of the underlying hook is a mutable object, they may modify that result but it's probably better to avoid it. -For more information, consult the `pluggy documentation <http://pluggy.readthedocs.io/en/latest/#wrappers>`_. +For more information, consult the +:ref:`pluggy documentation about hookwrappers <pluggy:hookwrappers>`. +.. _plugin-hookorder: Hook function ordering / call example ------------------------------------- @@ -612,6 +622,11 @@ among each other. Declaring new hooks ------------------------ +.. note:: + + This is a quick overview on how to add new hooks and how they work in general, but a more complete + overview can be found in `the pluggy documentation <https://pluggy.readthedocs.io/en/latest/>`__. + .. currentmodule:: _pytest.hookspec Plugins and ``conftest.py`` files may declare new hooks that can then be @@ -623,12 +638,111 @@ the new plugin: Hooks are usually declared as do-nothing functions that contain only documentation describing when the hook will be called and what return values -are expected. +are expected. The names of the functions must start with `pytest_` otherwise pytest won't recognize them. -For an example, see `newhooks.py`_ from `xdist <https://github.com/pytest-dev/pytest-xdist>`_. +Here's an example. Let's assume this code is in the ``sample_hook.py`` module. + +.. code-block:: python + + def pytest_my_hook(config): + """ + Receives the pytest config and does things with it + """ + +To register the hooks with pytest they need to be structured in their own module or class. This +class or module can then be passed to the ``pluginmanager`` using the ``pytest_addhooks`` function +(which itself is a hook exposed by pytest). + +.. code-block:: python + + def pytest_addhooks(pluginmanager): + """ This example assumes the hooks are grouped in the 'sample_hook' module. """ + from my_app.tests import sample_hook + + pluginmanager.add_hookspecs(sample_hook) + +For a real world example, see `newhooks.py`_ from `xdist <https://github.com/pytest-dev/pytest-xdist>`_. .. _`newhooks.py`: https://github.com/pytest-dev/pytest-xdist/blob/974bd566c599dc6a9ea291838c6f226197208b46/xdist/newhooks.py +Hooks may be called both from fixtures or from other hooks. In both cases, hooks are called +through the ``hook`` object, available in the ``config`` object. Most hooks receive a +``config`` object directly, while fixtures may use the ``pytestconfig`` fixture which provides the same object. + +.. code-block:: python + + @pytest.fixture() + def my_fixture(pytestconfig): + # call the hook called "pytest_my_hook" + # 'result' will be a list of return values from all registered functions. + result = pytestconfig.hook.pytest_my_hook(config=pytestconfig) + +.. note:: + Hooks receive parameters using only keyword arguments. + +Now your hook is ready to be used. To register a function at the hook, other plugins or users must +now simply define the function ``pytest_my_hook`` with the correct signature in their ``conftest.py``. + +Example: + +.. code-block:: python + + def pytest_my_hook(config): + """ + Print all active hooks to the screen. + """ + print(config.hook) + + +.. _`addoptionhooks`: + + +Using hooks in pytest_addoption +------------------------------- + +Occasionally, it is necessary to change the way in which command line options +are defined by one plugin based on hooks in another plugin. For example, +a plugin may expose a command line option for which another plugin needs +to define the default value. The pluginmanager can be used to install and +use hooks to accomplish this. The plugin would define and add the hooks +and use pytest_addoption as follows: + +.. code-block:: python + + # contents of hooks.py + + # Use firstresult=True because we only want one plugin to define this + # default value + @hookspec(firstresult=True) + def pytest_config_file_default_value(): + """ Return the default value for the config file command line option. """ + + + # contents of myplugin.py + + + def pytest_addhooks(pluginmanager): + """ This example assumes the hooks are grouped in the 'hooks' module. """ + from . import hook + + pluginmanager.add_hookspecs(hook) + + + def pytest_addoption(parser, pluginmanager): + default_value = pluginmanager.hook.pytest_config_file_default_value() + parser.addoption( + "--config-file", + help="Config file to use, defaults to %(default)s", + default=default_value, + ) + +The conftest.py that is using myplugin would simply define the hook as follows: + +.. code-block:: python + + def pytest_config_file_default_value(): + return "config.yaml" + Optionally using hooks from 3rd party plugins --------------------------------------------- @@ -646,7 +760,7 @@ declaring the hook functions directly in your plugin module, for example: # contents of myplugin.py - class DeferPlugin(object): + class DeferPlugin: """Simple plugin to defer pytest-xdist hook functions.""" def pytest_testnodedown(self, node, error): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/xunit_setup.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/xunit_setup.rst index 46687330255..8b3366f62ae 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/xunit_setup.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/doc/en/xunit_setup.rst @@ -12,7 +12,7 @@ fixtures (setup and teardown test state) on a per-module/class/function basis. .. note:: While these setup/teardown methods are simple and familiar to those - coming from a ``unittest`` or nose ``background``, you may also consider + coming from a ``unittest`` or ``nose`` background, you may also consider using pytest's more powerful :ref:`fixture mechanism <fixture>` which leverages the concept of dependency injection, allowing for a more modular and more scalable approach for managing test state, @@ -27,11 +27,14 @@ Module level setup/teardown If you have multiple test functions and test classes in a single module you can optionally implement the following fixture methods -which will usually be called once for all the functions:: +which will usually be called once for all the functions: + +.. code-block:: python def setup_module(module): """ setup any state specific to the execution of the given module.""" + def teardown_module(module): """ teardown any state that was previously setup with a setup_module method. @@ -43,7 +46,9 @@ Class level setup/teardown ---------------------------------- Similarly, the following methods are called at class level before -and after all test methods of the class are called:: +and after all test methods of the class are called: + +.. code-block:: python @classmethod def setup_class(cls): @@ -51,6 +56,7 @@ and after all test methods of the class are called:: usually contains tests). """ + @classmethod def teardown_class(cls): """ teardown any state that was previously setup with a call to @@ -60,13 +66,16 @@ and after all test methods of the class are called:: Method and function level setup/teardown ----------------------------------------------- -Similarly, the following methods are called around each method invocation:: +Similarly, the following methods are called around each method invocation: + +.. code-block:: python def setup_method(self, method): """ setup any state tied to the execution of the given method in a class. setup_method is invoked for every test method of a class. """ + def teardown_method(self, method): """ teardown any state that was previously setup with a setup_method call. @@ -75,13 +84,16 @@ Similarly, the following methods are called around each method invocation:: As of pytest-3.0, the ``method`` parameter is optional. If you would rather define test functions directly at module level -you can also use the following functions to implement fixtures:: +you can also use the following functions to implement fixtures: + +.. code-block:: python def setup_function(function): """ setup any state tied to the execution of the given function. Invoked for every test function in the module. """ + def teardown_function(function): """ teardown any state that was previously setup with a setup_function call. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/get_issues.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/get_issues.py index 598a1af409f..4aaa3c3ec31 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/get_issues.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/get_issues.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- import json +from pathlib import Path -import py import requests issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues" @@ -32,12 +31,12 @@ def get_issues(): def main(args): - cachefile = py.path.local(args.cache) + cachefile = Path(args.cache) if not cachefile.exists() or args.refresh: issues = get_issues() - cachefile.write(json.dumps(issues)) + cachefile.write_text(json.dumps(issues), "utf-8") else: - issues = json.loads(cachefile.read()) + issues = json.loads(cachefile.read_text("utf-8")) open_issues = [x for x in issues if x["state"] == "open"] @@ -46,7 +45,7 @@ def main(args): def _get_kind(issue): - labels = [l["name"] for l in issue["labels"]] + labels = [label["name"] for label in issue["labels"]] for key in ("bug", "enhancement", "proposal"): if key in labels: return key diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/setup-py.test/setup.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/setup-py.test/setup.py index cfe18c28f52..d0560ce1f5f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/setup-py.test/setup.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/extra/setup-py.test/setup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import sys from distutils.core import setup diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/pyproject.toml b/tests/wpt/web-platform-tests/tools/third_party/pytest/pyproject.toml index 2a4cd65c118..aee467fcf12 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/pyproject.toml +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/pyproject.toml @@ -1,23 +1,69 @@ [build-system] requires = [ # sync with setup.py until we discard non-pep-517/518 - "setuptools>=40.0", - "setuptools-scm", + "setuptools>=42.0", + "setuptools-scm[toml]>=3.4", "wheel", ] build-backend = "setuptools.build_meta" +[tool.setuptools_scm] +write_to = "src/_pytest/_version.py" + +[tool.pytest.ini_options] +minversion = "2.0" +addopts = "-rfEX -p pytester --strict-markers" +python_files = ["test_*.py", "*_test.py", "testing/python/*.py"] +python_classes = ["Test", "Acceptance"] +python_functions = ["test"] +# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting". +testpaths = ["testing"] +norecursedirs = ["testing/example_scripts"] +xfail_strict = true +filterwarnings = [ + "error", + "default:Using or importing the ABCs:DeprecationWarning:unittest2.*", + "default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*", + "ignore:Module already imported so cannot be rewritten:pytest.PytestWarning", + # produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8)." + "ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))", + # produced by pytest-xdist + "ignore:.*type argument to addoption.*:DeprecationWarning", + # produced by python >=3.5 on execnet (pytest-xdist) + "ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning", + # pytest's own futurewarnings + "ignore::pytest.PytestExperimentalApiWarning", + # Do not cause SyntaxError for invalid escape sequences in py37. + # Those are caught/handled by pyupgrade, and not easy to filter with the + # module being the filename (with .py removed). + "default:invalid escape sequence:DeprecationWarning", + # ignore use of unregistered marks, because we use many to test the implementation + "ignore::_pytest.warning_types.PytestUnknownMarkWarning", +] +pytester_example_dir = "testing/example_scripts" +markers = [ + # dummy markers for testing + "foo", + "bar", + "baz", + # conftest.py reorders tests moving slow ones to the end of the list + "slow", + # experimental mark for all tests using pexpect + "uses_pexpect", +] + + [tool.towncrier] package = "pytest" package_dir = "src" -filename = "CHANGELOG.rst" +filename = "doc/en/changelog.rst" directory = "changelog/" title_format = "pytest {version} ({project_date})" template = "changelog/_template.rst" [[tool.towncrier.type]] - directory = "removal" - name = "Removals" + directory = "breaking" + name = "Breaking Changes" showcontent = true [[tool.towncrier.type]] @@ -31,6 +77,11 @@ template = "changelog/_template.rst" showcontent = true [[tool.towncrier.type]] + directory = "improvement" + name = "Improvements" + showcontent = true + + [[tool.towncrier.type]] directory = "bugfix" name = "Bug Fixes" showcontent = true @@ -49,3 +100,6 @@ template = "changelog/_template.rst" directory = "trivial" name = "Trivial/Internal Changes" showcontent = true + +[tool.black] +target-version = ['py35'] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/append_codecov_token.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/append_codecov_token.py index b3a82075fa1..8eecb0fa51a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/append_codecov_token.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/append_codecov_token.py @@ -1,8 +1,9 @@ -# -*- coding: utf-8 -*- """ Appends the codecov token to the 'codecov.yml' file at the root of the repository. + This is done by CI during PRs and builds on the pytest-dev repository so we can upload coverage, at least until codecov grows some native integration like it has with Travis and AppVeyor. + See discussion in https://github.com/pytest-dev/pytest/pull/6441 for more information. """ import os.path diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/publish-gh-release-notes.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/publish-gh-release-notes.py new file mode 100644 index 00000000000..2531b0221b9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/publish-gh-release-notes.py @@ -0,0 +1,105 @@ +""" +Script used to publish GitHub release notes extracted from CHANGELOG.rst. + +This script is meant to be executed after a successful deployment in Travis. + +Uses the following environment variables: + +* GIT_TAG: the name of the tag of the current commit. +* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions. + + Create one at: + + https://github.com/settings/tokens + + It should be encrypted using: + + $travis encrypt GH_RELEASE_NOTES_TOKEN=<token> -r pytest-dev/pytest + + And the contents pasted in the ``deploy.env.secure`` section in the ``travis.yml`` file. + +The script also requires ``pandoc`` to be previously installed in the system. + +Requires Python3.6+. +""" +import os +import re +import sys +from pathlib import Path + +import github3 +import pypandoc + + +def publish_github_release(slug, token, tag_name, body): + github = github3.login(token=token) + owner, repo = slug.split("/") + repo = github.repository(owner, repo) + return repo.create_release(tag_name=tag_name, body=body) + + +def parse_changelog(tag_name): + p = Path(__file__).parent.parent / "doc/en/changelog.rst" + changelog_lines = p.read_text(encoding="UTF-8").splitlines() + + title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)") + consuming_version = False + version_lines = [] + for line in changelog_lines: + m = title_regex.match(line) + if m: + # found the version we want: start to consume lines until we find the next version title + if m.group(1) == tag_name: + consuming_version = True + # found a new version title while parsing the version we want: break out + elif consuming_version: + break + if consuming_version: + version_lines.append(line) + + return "\n".join(version_lines) + + +def convert_rst_to_md(text): + return pypandoc.convert_text( + text, "md", format="rst", extra_args=["--wrap=preserve"] + ) + + +def main(argv): + if len(argv) > 1: + tag_name = argv[1] + else: + tag_name = os.environ.get("GITHUB_REF") + if not tag_name: + print("tag_name not given and $GITHUB_REF not set", file=sys.stderr) + return 1 + if tag_name.startswith("refs/tags/"): + tag_name = tag_name[len("refs/tags/") :] + + token = os.environ.get("GH_RELEASE_NOTES_TOKEN") + if not token: + print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr) + return 1 + + slug = os.environ.get("GITHUB_REPOSITORY") + if not slug: + print("GITHUB_REPOSITORY not set", file=sys.stderr) + return 1 + + rst_body = parse_changelog(tag_name) + md_body = convert_rst_to_md(rst_body) + if not publish_github_release(slug, token, tag_name, md_body): + print("Could not publish release notes:", file=sys.stderr) + print(md_body, file=sys.stderr) + return 5 + + print() + print(f"Release notes for {tag_name} published successfully:") + print(f"https://github.com/{slug}/releases/tag/{tag_name}") + print() + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/publish_gh_release_notes.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/publish_gh_release_notes.py deleted file mode 100644 index 3ff946b58a3..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/publish_gh_release_notes.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Script used to publish GitHub release notes extracted from CHANGELOG.rst. - -This script is meant to be executed after a successful deployment in Travis. - -Uses the following environment variables: - -* GIT_TAG: the name of the tag of the current commit. -* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions. It should be encrypted using: - - $travis encrypt GH_RELEASE_NOTES_TOKEN=<token> -r pytest-dev/pytest - - And the contents pasted in the ``deploy.env.secure`` section in the ``travis.yml`` file. - -The script also requires ``pandoc`` to be previously installed in the system. - -Requires Python3.6+. -""" -import os -import re -import sys -from pathlib import Path - -import github3 -import pypandoc - - -def publish_github_release(slug, token, tag_name, body): - github = github3.login(token=token) - owner, repo = slug.split("/") - repo = github.repository(owner, repo) - return repo.create_release(tag_name=tag_name, body=body) - - -def parse_changelog(tag_name): - p = Path(__file__).parent.parent / "CHANGELOG.rst" - changelog_lines = p.read_text(encoding="UTF-8").splitlines() - - title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)") - consuming_version = False - version_lines = [] - for line in changelog_lines: - m = title_regex.match(line) - if m: - # found the version we want: start to consume lines until we find the next version title - if m.group(1) == tag_name: - consuming_version = True - # found a new version title while parsing the version we want: break out - elif consuming_version: - break - if consuming_version: - version_lines.append(line) - - return "\n".join(version_lines) - - -def convert_rst_to_md(text): - return pypandoc.convert_text(text, "md", format="rst") - - -def main(argv): - if len(argv) > 1: - tag_name = argv[1] - else: - tag_name = os.environ.get("TRAVIS_TAG") - if not tag_name: - print("tag_name not given and $TRAVIS_TAG not set", file=sys.stderr) - return 1 - - token = os.environ.get("GH_RELEASE_NOTES_TOKEN") - if not token: - print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr) - return 1 - - slug = os.environ.get("TRAVIS_REPO_SLUG") - if not slug: - print("TRAVIS_REPO_SLUG not set", file=sys.stderr) - return 1 - - rst_body = parse_changelog(tag_name) - md_body = convert_rst_to_md(rst_body) - if not publish_github_release(slug, token, tag_name, md_body): - print("Could not publish release notes:", file=sys.stderr) - print(md_body, file=sys.stderr) - return 5 - - print() - print(f"Release notes for {tag_name} published successfully:") - print(f"https://github.com/{slug}/releases/tag/{tag_name}") - print() - return 0 - - -if __name__ == "__main__": - sys.exit(main(sys.argv)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release-on-comment.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release-on-comment.py new file mode 100644 index 00000000000..7c662113e06 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release-on-comment.py @@ -0,0 +1,254 @@ +""" +This script is part of the pytest release process which is triggered by comments +in issues. + +This script is started by the `release-on-comment.yml` workflow, which always executes on +`master` and is triggered by two comment related events: + +* https://help.github.com/en/actions/reference/events-that-trigger-workflows#issue-comment-event-issue_comment +* https://help.github.com/en/actions/reference/events-that-trigger-workflows#issues-event-issues + +This script receives the payload and a secrets on the command line. + +The payload must contain a comment with a phrase matching this pseudo-regular expression: + + @pytestbot please prepare (major )? release from <branch name> + +Then the appropriate version will be obtained based on the given branch name: + +* a major release from master if "major" appears in the phrase in that position +* a feature or bug fix release from master (based if there are features in the current changelog + folder) +* a bug fix from a maintenance branch + +After that, it will create a release using the `release` tox environment, and push a new PR. + +**Secret**: currently the secret is defined in the @pytestbot account, which the core maintainers +have access to. There we created a new secret named `chatops` with write access to the repository. +""" +import argparse +import json +import os +import re +import traceback +from pathlib import Path +from subprocess import CalledProcessError +from subprocess import check_call +from subprocess import check_output +from subprocess import run +from textwrap import dedent +from typing import Dict +from typing import Optional +from typing import Tuple + +from colorama import Fore +from colorama import init +from github3.repos import Repository + + +class InvalidFeatureRelease(Exception): + pass + + +SLUG = "pytest-dev/pytest" + +PR_BODY = """\ +Created automatically from {comment_url}. + +Once all builds pass and it has been **approved** by one or more maintainers, the build +can be released by pushing a tag `{version}` to this repository. +""" + + +def login(token: str) -> Repository: + import github3 + + github = github3.login(token=token) + owner, repo = SLUG.split("/") + return github.repository(owner, repo) + + +def get_comment_data(payload: Dict) -> str: + if "comment" in payload: + return payload["comment"] + else: + return payload["issue"] + + +def validate_and_get_issue_comment_payload( + issue_payload_path: Optional[Path], +) -> Tuple[str, str, bool]: + payload = json.loads(issue_payload_path.read_text(encoding="UTF-8")) + body = get_comment_data(payload)["body"] + m = re.match(r"@pytestbot please prepare (major )?release from ([\w\-_\.]+)", body) + if m: + is_major, base_branch = m.group(1) is not None, m.group(2) + else: + is_major, base_branch = False, None + return payload, base_branch, is_major + + +def print_and_exit(msg) -> None: + print(msg) + raise SystemExit(1) + + +def trigger_release(payload_path: Path, token: str) -> None: + payload, base_branch, is_major = validate_and_get_issue_comment_payload( + payload_path + ) + if base_branch is None: + url = get_comment_data(payload)["html_url"] + print_and_exit( + f"Comment {Fore.CYAN}{url}{Fore.RESET} did not match the trigger command." + ) + print() + print(f"Precessing release for branch {Fore.CYAN}{base_branch}") + + repo = login(token) + + issue_number = payload["issue"]["number"] + issue = repo.issue(issue_number) + + check_call(["git", "checkout", f"origin/{base_branch}"]) + + try: + version = find_next_version(base_branch, is_major) + except InvalidFeatureRelease as e: + issue.create_comment(str(e)) + print_and_exit(f"{Fore.RED}{e}") + + error_contents = "" + try: + print(f"Version: {Fore.CYAN}{version}") + + release_branch = f"release-{version}" + + run( + ["git", "config", "user.name", "pytest bot"], + text=True, + check=True, + capture_output=True, + ) + run( + ["git", "config", "user.email", "pytestbot@gmail.com"], + text=True, + check=True, + capture_output=True, + ) + + run( + ["git", "checkout", "-b", release_branch, f"origin/{base_branch}"], + text=True, + check=True, + capture_output=True, + ) + + print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.") + + # important to use tox here because we have changed branches, so dependencies + # might have changed as well + cmdline = ["tox", "-e", "release", "--", version, "--skip-check-links"] + print("Running", " ".join(cmdline)) + run( + cmdline, text=True, check=True, capture_output=True, + ) + + oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git" + run( + ["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"], + text=True, + check=True, + capture_output=True, + ) + print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} pushed.") + + body = PR_BODY.format( + comment_url=get_comment_data(payload)["html_url"], version=version + ) + pr = repo.create_pull( + f"Prepare release {version}", + base=base_branch, + head=release_branch, + body=body, + ) + print(f"Pull request {Fore.CYAN}{pr.url}{Fore.RESET} created.") + + comment = issue.create_comment( + f"As requested, opened a PR for release `{version}`: #{pr.number}." + ) + print(f"Notified in original comment {Fore.CYAN}{comment.url}{Fore.RESET}.") + + except CalledProcessError as e: + error_contents = f"CalledProcessError\noutput:\n{e.output}\nstderr:\n{e.stderr}" + except Exception: + error_contents = f"Exception:\n{traceback.format_exc()}" + + if error_contents: + link = f"https://github.com/{SLUG}/actions/runs/{os.environ['GITHUB_RUN_ID']}" + msg = ERROR_COMMENT.format( + version=version, base_branch=base_branch, contents=error_contents, link=link + ) + issue.create_comment(msg) + print_and_exit(f"{Fore.RED}{error_contents}") + else: + print(f"{Fore.GREEN}Success.") + + +ERROR_COMMENT = """\ +The request to prepare release `{version}` from {base_branch} failed with: + +``` +{contents} +``` + +See: {link}. +""" + + +def find_next_version(base_branch: str, is_major: bool) -> str: + output = check_output(["git", "tag"], encoding="UTF-8") + valid_versions = [] + for v in output.splitlines(): + m = re.match(r"\d.\d.\d+$", v.strip()) + if m: + valid_versions.append(tuple(int(x) for x in v.split("."))) + + valid_versions.sort() + last_version = valid_versions[-1] + + changelog = Path("changelog") + + features = list(changelog.glob("*.feature.rst")) + breaking = list(changelog.glob("*.breaking.rst")) + is_feature_release = features or breaking + + if is_feature_release and base_branch != "master": + msg = dedent( + f""" + Found features or breaking changes in `{base_branch}`, and feature releases can only be + created from `master`.": + """ + ) + msg += "\n".join(f"* `{x.name}`" for x in sorted(features + breaking)) + raise InvalidFeatureRelease(msg) + + if is_major: + return f"{last_version[0]+1}.0.0" + elif is_feature_release: + return f"{last_version[0]}.{last_version[1] + 1}.0" + else: + return f"{last_version[0]}.{last_version[1]}.{last_version[2] + 1}" + + +def main() -> None: + init(autoreset=True) + parser = argparse.ArgumentParser() + parser.add_argument("payload") + parser.add_argument("token") + options = parser.parse_args() + trigger_release(Path(options.payload), options.token) + + +if __name__ == "__main__": + main() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.minor.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.minor.rst index 9a488edbc52..76e447f0c6d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.minor.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.minor.rst @@ -3,25 +3,22 @@ pytest-{version} The pytest team is proud to announce the {version} release! -pytest is a mature Python testing tool with more than a 2000 tests -against itself, passing on many different interpreters and platforms. +This release contains new features, improvements, bug fixes, and breaking changes, so users +are encouraged to take a look at the CHANGELOG carefully: -This release contains a number of bugs fixes and improvements, so users are encouraged -to take a look at the CHANGELOG: - - https://docs.pytest.org/en/latest/changelog.html + https://docs.pytest.org/en/stable/changelog.html For complete documentation, please visit: - https://docs.pytest.org/en/latest/ + https://docs.pytest.org/en/stable/ -As usual, you can upgrade from pypi via: +As usual, you can upgrade from PyPI via: pip install -U pytest -Thanks to all who contributed to this release, among them: +Thanks to all of the contributors to this release: {contributors} Happy testing, -The Pytest Development Team +The pytest Development Team diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.patch.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.patch.rst index b1ad2dbd775..59fbe50ce0e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.patch.rst +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.patch.rst @@ -7,9 +7,9 @@ This is a bug-fix release, being a drop-in replacement. To upgrade:: pip install --upgrade pytest -The full changelog is available at https://docs.pytest.org/en/latest/changelog.html. +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. -Thanks to all who contributed to this release, among them: +Thanks to all of the contributors to this release: {contributors} diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.py index 32178fcf674..5e3158ab52b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/release.py @@ -1,8 +1,6 @@ -# -*- coding: utf-8 -*- -""" -Invoke development tasks. -""" +"""Invoke development tasks.""" import argparse +import os from pathlib import Path from subprocess import call from subprocess import check_call @@ -66,10 +64,13 @@ def announce(version): check_call(["git", "add", str(target)]) -def regen(): +def regen(version): """Call regendoc tool to update examples and pytest output in the docs.""" print(f"{Fore.CYAN}[generate.regen] {Fore.RESET}Updating docs") - check_call(["tox", "-e", "regen"]) + check_call( + ["tox", "-e", "regen"], + env={**os.environ, "SETUPTOOLS_SCM_PRETEND_VERSION": version}, + ) def fix_formatting(): @@ -80,20 +81,28 @@ def fix_formatting(): call(["pre-commit", "run", "--all-files"]) -def pre_release(version): +def check_links(): + """Runs sphinx-build to check links""" + print(f"{Fore.CYAN}[generate.check_links] {Fore.RESET}Checking links") + check_call(["tox", "-e", "docs-checklinks"]) + + +def pre_release(version, *, skip_check_links): """Generates new docs, release announcements and creates a local tag.""" announce(version) - regen() + regen(version) changelog(version, write_out=True) fix_formatting() + if not skip_check_links: + check_links() - msg = "Preparing release version {}".format(version) + msg = "Prepare release version {}".format(version) check_call(["git", "commit", "-a", "-m", msg]) print() print(f"{Fore.CYAN}[generate.pre_release] {Fore.GREEN}All done!") print() - print(f"Please push your branch and open a PR.") + print("Please push your branch and open a PR.") def changelog(version, write_out=False): @@ -108,8 +117,9 @@ def main(): init(autoreset=True) parser = argparse.ArgumentParser() parser.add_argument("version", help="Release version") + parser.add_argument("--skip-check-links", action="store_true", default=False) options = parser.parse_args() - pre_release(options.version) + pre_release(options.version, skip_check_links=options.skip_check_links) if __name__ == "__main__": diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/retry.cmd b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/retry.cmd deleted file mode 100644 index ac383650857..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/retry.cmd +++ /dev/null @@ -1,21 +0,0 @@ -@echo off -rem Source: https://github.com/appveyor/ci/blob/master/scripts/appveyor-retry.cmd -rem initiate the retry number -set retryNumber=0 -set maxRetries=3 - -:RUN -%* -set LastErrorLevel=%ERRORLEVEL% -IF %LastErrorLevel% == 0 GOTO :EOF -set /a retryNumber=%retryNumber%+1 -IF %reTryNumber% == %maxRetries% (GOTO :FAILED) - -:RETRY -set /a retryNumberDisp=%retryNumber%+1 -@echo Command "%*" failed with exit code %LastErrorLevel%. Retrying %retryNumberDisp% of %maxRetries% -GOTO :RUN - -: FAILED -@echo Sorry, we tried running command for %maxRetries% times and all attempts were unsuccessful! -EXIT /B %LastErrorLevel% diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/setup-coverage-vars.bat b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/setup-coverage-vars.bat deleted file mode 100644 index 7a4a6d4deb8..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/setup-coverage-vars.bat +++ /dev/null @@ -1,7 +0,0 @@ -if "%PYTEST_COVERAGE%" == "1" ( - set "_PYTEST_TOX_COVERAGE_RUN=coverage run -m" - set "_PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess" - echo Coverage vars configured, PYTEST_COVERAGE=%PYTEST_COVERAGE% -) else ( - echo Skipping coverage vars setup, PYTEST_COVERAGE=%PYTEST_COVERAGE% -) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py new file mode 100644 index 00000000000..81507b40b75 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py @@ -0,0 +1,15 @@ +import sys +from subprocess import call + + +def main(): + """ + Platform agnostic wrapper script for towncrier. + Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs. + """ + with open("doc/en/_changelog_towncrier_draft.rst", "w") as draft_file: + return call(("towncrier", "--draft"), stdout=draft_file) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/upload-coverage.bat b/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/upload-coverage.bat deleted file mode 100644 index 08ed5779122..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/scripts/upload-coverage.bat +++ /dev/null @@ -1,16 +0,0 @@ -REM script called by Azure to combine and upload coverage information to codecov -if "%PYTEST_COVERAGE%" == "1" ( - echo Prepare to upload coverage information - if defined CODECOV_TOKEN ( - echo CODECOV_TOKEN defined - ) else ( - echo CODECOV_TOKEN NOT defined - ) - python -m pip install codecov - python -m coverage combine - python -m coverage xml - python -m coverage report -m - scripts\retry python -m codecov --required -X gcov pycov search -f coverage.xml --name %PYTEST_CODECOV_NAME% -) else ( - echo Skipping coverage upload, PYTEST_COVERAGE=%PYTEST_COVERAGE% -) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.cfg b/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.cfg index 368df1e1ac0..60f65649aac 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.cfg +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.cfg @@ -1,57 +1,82 @@ [metadata] - name = pytest description = pytest: simple powerful testing with Python long_description = file: README.rst +long_description_content_type = text/x-rst url = https://docs.pytest.org/en/latest/ -project_urls = - Source=https://github.com/pytest-dev/pytest - Tracker=https://github.com/pytest-dev/pytest/issues - author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others - -license = MIT license +license = MIT license_file = LICENSE -keywords = test, unittest -classifiers = - Development Status :: 6 - Mature - Intended Audience :: Developers - License :: OSI Approved :: MIT License - Operating System :: POSIX - Operating System :: Microsoft :: Windows - Operating System :: MacOS :: MacOS X - Topic :: Software Development :: Testing - Topic :: Software Development :: Libraries - Topic :: Utilities - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 platforms = unix, linux, osx, cygwin, win32 +classifiers = + Development Status :: 6 - Mature + Intended Audience :: Developers + License :: OSI Approved :: MIT License + Operating System :: MacOS :: MacOS X + Operating System :: Microsoft :: Windows + Operating System :: POSIX + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Topic :: Software Development :: Libraries + Topic :: Software Development :: Testing + Topic :: Utilities +keywords = test, unittest +project_urls = + Source=https://github.com/pytest-dev/pytest + Tracker=https://github.com/pytest-dev/pytest/issues [options] +packages = + _pytest + _pytest._code + _pytest._io + _pytest.assertion + _pytest.config + _pytest.mark + pytest +install_requires = + attrs>=17.4.0 + iniconfig + packaging + pluggy>=0.12,<1.0 + py>=1.8.2 + toml + atomicwrites>=1.0;sys_platform=="win32" + colorama;sys_platform=="win32" + importlib-metadata>=0.12;python_version<"3.8" + pathlib2>=2.2.0;python_version<"3.6" +python_requires = >=3.5 +package_dir = + =src +setup_requires = + setuptools>=40.0 + setuptools-scm zip_safe = no -packages = - _pytest - _pytest._code - _pytest._io - _pytest.assertion - _pytest.config - _pytest.mark -py_modules = pytest -python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +[options.entry_points] +console_scripts = + pytest=pytest:console_main + py.test=pytest:console_main +[options.extras_require] +checkqa-mypy = + mypy==0.780 +testing = + argcomplete + hypothesis>=3.56 + mock + nose + requests + xmlschema -[options.entry_points] -console_scripts = - pytest=pytest:main - py.test=pytest:main +[options.package_data] +_pytest = py.typed +pytest = py.typed [build_sphinx] source-dir = doc/en/ @@ -61,13 +86,28 @@ all_files = 1 [upload_sphinx] upload-dir = doc/en/build/html -[bdist_wheel] -universal = 1 - [check-manifest] -ignore = - _pytest/_version.py - +ignore = + src/_pytest/_version.py [devpi:upload] formats = sdist.tgz,bdist_wheel + +[mypy] +mypy_path = src +check_untyped_defs = True +disallow_any_generics = True +ignore_missing_imports = True +no_implicit_optional = True +show_error_codes = True +strict_equality = True +warn_redundant_casts = True +warn_return_any = True +warn_unreachable = True +warn_unused_configs = True +no_implicit_reexport = True + +[egg_info] +tag_build = +tag_date = 0 + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.py index 1053bce4a5a..7f1a1763ca9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/setup.py @@ -1,45 +1,4 @@ -# -*- coding: utf-8 -*- from setuptools import setup -# TODO: if py gets upgrade to >=1.6, -# remove _width_of_current_line in terminal.py -INSTALL_REQUIRES = [ - "py>=1.5.0", - "six>=1.10.0", - "packaging", - "attrs>=17.4.0", - 'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"', - 'more-itertools>=4.0.0;python_version>"2.7"', - "atomicwrites>=1.0", - 'funcsigs>=1.0;python_version<"3.0"', - 'pathlib2>=2.2.0;python_version<"3.6"', - 'colorama<=0.4.1;sys_platform=="win32" and python_version=="3.4"', - 'colorama;sys_platform=="win32" and python_version!="3.4"', - "pluggy>=0.12,<1.0", - 'importlib-metadata>=0.12;python_version<"3.8"', - "wcwidth", -] - - -def main(): - setup( - use_scm_version={"write_to": "src/_pytest/_version.py"}, - setup_requires=["setuptools-scm", "setuptools>=40.0"], - package_dir={"": "src"}, - # fmt: off - extras_require={ - "testing": [ - "argcomplete", - "hypothesis>=3.56", - "nose", - "requests", - "mock;python_version=='2.7'", - ], - }, - # fmt: on - install_requires=INSTALL_REQUIRES, - ) - - if __name__ == "__main__": - main() + setup() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py index 17cc20b615a..46c7827ed5e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- __all__ = ["__version__"] try: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py index c6cf1d8fddd..3dbdf9318be 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_argcomplete.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- -"""allow bash-completion for argparse with argcomplete if installed -needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail +"""Allow bash-completion for argparse with argcomplete if installed. + +Needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail to find the magic string, so _ARGCOMPLETE env. var is never set, and -this does not need special code. +this does not need special code). Function try_argcomplete(parser) should be called directly before the call to ArgumentParser.parse_args(). @@ -11,8 +11,7 @@ The filescompleter is what you normally would use on the positional arguments specification, in order to get "dirname/" after "dirn<TAB>" instead of the default "dirname ": - optparser.add_argument(Config._file_or_dir, nargs='*' - ).completer=filescompleter + optparser.add_argument(Config._file_or_dir, nargs='*').completer=filescompleter Other, application specific, completers should go in the file doing the add_argument calls as they need to be specified as .completer @@ -21,56 +20,64 @@ attribute points to will not be used). SPEEDUP ======= + The generic argcomplete script for bash-completion -(/etc/bash_completion.d/python-argcomplete.sh ) +(/etc/bash_completion.d/python-argcomplete.sh) uses a python program to determine startup script generated by pip. You can speed up completion somewhat by changing this script to include # PYTHON_ARGCOMPLETE_OK so the the python-argcomplete-check-easy-install-script does not need to be called to find the entry point of the code and see if that is -marked with PYTHON_ARGCOMPLETE_OK +marked with PYTHON_ARGCOMPLETE_OK. INSTALL/DEBUGGING ================= + To include this support in another application that has setup.py generated scripts: -- add the line: + +- Add the line: # PYTHON_ARGCOMPLETE_OK - near the top of the main python entry point -- include in the file calling parse_args(): + near the top of the main python entry point. + +- Include in the file calling parse_args(): from _argcomplete import try_argcomplete, filescompleter - , call try_argcomplete just before parse_args(), and optionally add - filescompleter to the positional arguments' add_argument() + Call try_argcomplete just before parse_args(), and optionally add + filescompleter to the positional arguments' add_argument(). + If things do not work right away: -- switch on argcomplete debugging with (also helpful when doing custom + +- Switch on argcomplete debugging with (also helpful when doing custom completers): export _ARC_DEBUG=1 -- run: + +- Run: python-argcomplete-check-easy-install-script $(which appname) echo $? - will echo 0 if the magic line has been found, 1 if not -- sometimes it helps to find early on errors using: + will echo 0 if the magic line has been found, 1 if not. + +- Sometimes it helps to find early on errors using: _ARGCOMPLETE=1 _ARC_DEBUG=1 appname which should throw a KeyError: 'COMPLINE' (which is properly set by the global argcomplete script). """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import argparse import os import sys from glob import glob +from typing import Any +from typing import List +from typing import Optional -class FastFilesCompleter(object): - "Fast file completer class" +class FastFilesCompleter: + """Fast file completer class.""" - def __init__(self, directories=True): + def __init__(self, directories: bool = True) -> None: self.directories = directories - def __call__(self, prefix, **kwargs): - """only called on non option completions""" + def __call__(self, prefix: str, **kwargs: Any) -> List[str]: + # Only called on non option completions. if os.path.sep in prefix[1:]: prefix_dir = len(os.path.dirname(prefix) + os.path.sep) else: @@ -78,7 +85,7 @@ class FastFilesCompleter(object): completion = [] globbed = [] if "*" not in prefix and "?" not in prefix: - # we are on unix, otherwise no bash + # We are on unix, otherwise no bash. if not prefix or prefix[-1] == os.path.sep: globbed.extend(glob(prefix + ".*")) prefix += "*" @@ -86,7 +93,7 @@ class FastFilesCompleter(object): for x in sorted(globbed): if os.path.isdir(x): x += "/" - # append stripping the prefix (like bash, not like compgen) + # Append stripping the prefix (like bash, not like compgen). completion.append(x[prefix_dir:]) return completion @@ -96,15 +103,15 @@ if os.environ.get("_ARGCOMPLETE"): import argcomplete.completers except ImportError: sys.exit(-1) - filescompleter = FastFilesCompleter() + filescompleter = FastFilesCompleter() # type: Optional[FastFilesCompleter] - def try_argcomplete(parser): + def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) else: - def try_argcomplete(parser): + def try_argcomplete(parser: argparse.ArgumentParser) -> None: pass filescompleter = None diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py index 1394b2b10e6..511d0dde661 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/__init__.py @@ -1,15 +1,22 @@ -# -*- coding: utf-8 -*- -""" python inspection/code generation API """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +"""Python inspection/code generation API.""" +from .code import Code +from .code import ExceptionInfo +from .code import filter_traceback +from .code import Frame +from .code import getfslineno +from .code import Traceback +from .code import TracebackEntry +from .source import getrawcode +from .source import Source -from .code import Code # noqa -from .code import ExceptionInfo # noqa -from .code import filter_traceback # noqa -from .code import Frame # noqa -from .code import getrawcode # noqa -from .code import Traceback # noqa -from .source import compile_ as compile # noqa -from .source import getfslineno # noqa -from .source import Source # noqa +__all__ = [ + "Code", + "ExceptionInfo", + "filter_traceback", + "Frame", + "getfslineno", + "getrawcode", + "Traceback", + "TracebackEntry", + "Source", +] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/_py2traceback.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/_py2traceback.py deleted file mode 100644 index faacc02166e..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/_py2traceback.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -# copied from python-2.7.3's traceback.py -# CHANGES: -# - some_str is replaced, trying to create unicode strings -# -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -import types - -from six import text_type - - -def format_exception_only(etype, value): - """Format the exception part of a traceback. - - The arguments are the exception type and value such as given by - sys.last_type and sys.last_value. The return value is a list of - strings, each ending in a newline. - - Normally, the list contains a single string; however, for - SyntaxError exceptions, it contains several lines that (when - printed) display detailed information about where the syntax - error occurred. - - The message indicating which exception occurred is always the last - string in the list. - - """ - - # An instance should not have a meaningful value parameter, but - # sometimes does, particularly for string exceptions, such as - # >>> raise string1, string2 # deprecated - # - # Clear these out first because issubtype(string1, SyntaxError) - # would throw another exception and mask the original problem. - if ( - isinstance(etype, BaseException) - or isinstance(etype, types.InstanceType) - or etype is None - or type(etype) is str - ): - return [_format_final_exc_line(etype, value)] - - stype = etype.__name__ - - if not issubclass(etype, SyntaxError): - return [_format_final_exc_line(stype, value)] - - # It was a syntax error; show exactly where the problem was found. - lines = [] - try: - msg, (filename, lineno, offset, badline) = value.args - except Exception: - pass - else: - filename = filename or "<string>" - lines.append(' File "{}", line {}\n'.format(filename, lineno)) - if badline is not None: - if isinstance(badline, bytes): # python 2 only - badline = badline.decode("utf-8", "replace") - lines.append(" {}\n".format(badline.strip())) - if offset is not None: - caretspace = badline.rstrip("\n")[:offset].lstrip() - # non-space whitespace (likes tabs) must be kept for alignment - caretspace = ((c.isspace() and c or " ") for c in caretspace) - # only three spaces to account for offset1 == pos 0 - lines.append(" {}^\n".format("".join(caretspace))) - value = msg - - lines.append(_format_final_exc_line(stype, value)) - return lines - - -def _format_final_exc_line(etype, value): - """Return a list of a single line -- normal case for format_exception_only""" - valuestr = _some_str(value) - if value is None or not valuestr: - line = "{}\n".format(etype) - else: - line = "{}: {}\n".format(etype, valuestr) - return line - - -def _some_str(value): - try: - return text_type(value) - except Exception: - try: - return bytes(value).decode("UTF-8", "replace") - except Exception: - pass - return "<unprintable {} object>".format(type(value).__name__) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py index 175d6fda01d..5063e660477 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/code.py @@ -1,97 +1,111 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import inspect import re import sys import traceback from inspect import CO_VARARGS from inspect import CO_VARKEYWORDS +from io import StringIO +from traceback import format_exception_only +from types import CodeType +from types import FrameType +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generic +from typing import Iterable +from typing import List +from typing import Mapping +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TypeVar +from typing import Union from weakref import ref import attr import pluggy import py -from six import text_type import _pytest +from _pytest._code.source import findsource +from _pytest._code.source import getrawcode +from _pytest._code.source import getstatementrange_ast +from _pytest._code.source import Source +from _pytest._io import TerminalWriter from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr -from _pytest.compat import _PY2 -from _pytest.compat import _PY3 -from _pytest.compat import PY35 -from _pytest.compat import safe_str +from _pytest.compat import ATTRS_EQ_FIELD +from _pytest.compat import final +from _pytest.compat import get_real_func +from _pytest.compat import overload +from _pytest.compat import TYPE_CHECKING +from _pytest.pathlib import Path + +if TYPE_CHECKING: + from typing import Type + from typing_extensions import Literal + from weakref import ReferenceType -if _PY3: - from traceback import format_exception_only -else: - from ._py2traceback import format_exception_only + _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] -class Code(object): - """ wrapper around Python code objects """ +class Code: + """Wrapper around Python code objects.""" - def __init__(self, rawcode): + def __init__(self, rawcode) -> None: if not hasattr(rawcode, "co_filename"): rawcode = getrawcode(rawcode) - try: - self.filename = rawcode.co_filename - self.firstlineno = rawcode.co_firstlineno - 1 - self.name = rawcode.co_name - except AttributeError: - raise TypeError("not a code object: %r" % (rawcode,)) + if not isinstance(rawcode, CodeType): + raise TypeError("not a code object: {!r}".format(rawcode)) + self.filename = rawcode.co_filename + self.firstlineno = rawcode.co_firstlineno - 1 + self.name = rawcode.co_name self.raw = rawcode def __eq__(self, other): return self.raw == other.raw - __hash__ = None - - def __ne__(self, other): - return not self == other + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore @property - def path(self): - """ return a path object pointing to source code (note that it - might not point to an actually existing file). """ + def path(self) -> Union[py.path.local, str]: + """Return a path object pointing to source code, or an ``str`` in + case of ``OSError`` / non-existing file.""" + if not self.raw.co_filename: + return "" try: p = py.path.local(self.raw.co_filename) # maybe don't try this checking if not p.check(): raise OSError("py.path check failed.") + return p except OSError: # XXX maybe try harder like the weird logic # in the standard lib [linecache.updatecache] does? - p = self.raw.co_filename - - return p + return self.raw.co_filename @property - def fullsource(self): - """ return a _pytest._code.Source object for the full source file of the code - """ - from _pytest._code import source - - full, _ = source.findsource(self.raw) + def fullsource(self) -> Optional["Source"]: + """Return a _pytest._code.Source object for the full source file of the code.""" + full, _ = findsource(self.raw) return full - def source(self): - """ return a _pytest._code.Source object for the code object's source only - """ + def source(self) -> "Source": + """Return a _pytest._code.Source object for the code object's source only.""" # return source only for that part of code - import _pytest._code - - return _pytest._code.Source(self.raw) + return Source(self.raw) - def getargs(self, var=False): - """ return a tuple with the argument names for the code object + def getargs(self, var: bool = False) -> Tuple[str, ...]: + """Return a tuple with the argument names for the code object. - if 'var' is set True also return the names of the variable and - keyword arguments when present + If 'var' is set True also return the names of the variable and + keyword arguments when present. """ - # handfull shortcut for getting args + # Handy shortcut for getting args. raw = self.raw argcount = raw.co_argcount if var: @@ -100,11 +114,11 @@ class Code(object): return raw.co_varnames[:argcount] -class Frame(object): +class Frame: """Wrapper around a Python frame holding f_locals and f_globals in which expressions can be evaluated.""" - def __init__(self, frame): + def __init__(self, frame: FrameType) -> None: self.lineno = frame.f_lineno - 1 self.f_globals = frame.f_globals self.f_locals = frame.f_locals @@ -112,47 +126,32 @@ class Frame(object): self.code = Code(frame.f_code) @property - def statement(self): - """ statement this frame is at """ - import _pytest._code - + def statement(self) -> "Source": + """Statement this frame is at.""" if self.code.fullsource is None: - return _pytest._code.Source("") + return Source("") return self.code.fullsource.getstatement(self.lineno) def eval(self, code, **vars): - """ evaluate 'code' in the frame + """Evaluate 'code' in the frame. - 'vars' are optional additional local variables + 'vars' are optional additional local variables. - returns the result of the evaluation + Returns the result of the evaluation. """ f_locals = self.f_locals.copy() f_locals.update(vars) return eval(code, self.f_globals, f_locals) - def exec_(self, code, **vars): - """ exec 'code' in the frame - - 'vars' are optiona; additional local variables - """ - f_locals = self.f_locals.copy() - f_locals.update(vars) - exec(code, self.f_globals, f_locals) - - def repr(self, object): - """ return a 'safe' (non-recursive, one-line) string repr for 'object' - """ + def repr(self, object: object) -> str: + """Return a 'safe' (non-recursive, one-line) string repr for 'object'.""" return saferepr(object) - def is_true(self, object): - return object + def getargs(self, var: bool = False): + """Return a list of tuples (name, value) for all arguments. - def getargs(self, var=False): - """ return a list of tuples (name, value) for all arguments - - if 'var' is set True also include the variable and keyword - arguments when present + If 'var' is set True, also include the variable and keyword arguments + when present. """ retval = [] for arg in self.code.getargs(var): @@ -163,60 +162,60 @@ class Frame(object): return retval -class TracebackEntry(object): - """ a single entry in a traceback """ +class TracebackEntry: + """A single entry in a Traceback.""" - _repr_style = None + _repr_style = None # type: Optional[Literal["short", "long"]] exprinfo = None - def __init__(self, rawentry, excinfo=None): + def __init__( + self, + rawentry: TracebackType, + excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, + ) -> None: self._excinfo = excinfo self._rawentry = rawentry self.lineno = rawentry.tb_lineno - 1 - def set_repr_style(self, mode): + def set_repr_style(self, mode: "Literal['short', 'long']") -> None: assert mode in ("short", "long") self._repr_style = mode @property - def frame(self): - import _pytest._code - - return _pytest._code.Frame(self._rawentry.tb_frame) + def frame(self) -> Frame: + return Frame(self._rawentry.tb_frame) @property - def relline(self): + def relline(self) -> int: return self.lineno - self.frame.code.firstlineno - def __repr__(self): + def __repr__(self) -> str: return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1) @property - def statement(self): - """ _pytest._code.Source object for the current statement """ + def statement(self) -> "Source": + """_pytest._code.Source object for the current statement.""" source = self.frame.code.fullsource + assert source is not None return source.getstatement(self.lineno) @property - def path(self): - """ path to the source code """ + def path(self) -> Union[py.path.local, str]: + """Path to the source code.""" return self.frame.code.path - def getlocals(self): + @property + def locals(self) -> Dict[str, Any]: + """Locals of underlying frame.""" return self.frame.f_locals - locals = property(getlocals, None, None, "locals of underlaying frame") - - def getfirstlinesource(self): - # on Jython this firstlineno can be -1 apparently - return max(self.frame.code.firstlineno, 0) + def getfirstlinesource(self) -> int: + return self.frame.code.firstlineno - def getsource(self, astcache=None): - """ return failing source code. """ + def getsource(self, astcache=None) -> Optional["Source"]: + """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing - from _pytest._code.source import getstatementrange_ast - source = self.frame.code.fullsource if source is None: return None @@ -239,73 +238,94 @@ class TracebackEntry(object): source = property(getsource) - def ishidden(self): - """ return True if the current frame has a var __tracebackhide__ - resolving to True. + def ishidden(self) -> bool: + """Return True if the current frame has a var __tracebackhide__ + resolving to True. - If __tracebackhide__ is a callable, it gets called with the - ExceptionInfo instance and can decide whether to hide the traceback. + If __tracebackhide__ is a callable, it gets called with the + ExceptionInfo instance and can decide whether to hide the traceback. - mostly for internal use + Mostly for internal use. """ - f = self.frame - tbh = f.f_locals.get( - "__tracebackhide__", f.f_globals.get("__tracebackhide__", False) - ) + tbh = ( + False + ) # type: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] + for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): + # in normal cases, f_locals and f_globals are dictionaries + # however via `exec(...)` / `eval(...)` they can be other types + # (even incorrect types!). + # as such, we suppress all exceptions while accessing __tracebackhide__ + try: + tbh = maybe_ns_dct["__tracebackhide__"] + except Exception: + pass + else: + break if tbh and callable(tbh): return tbh(None if self._excinfo is None else self._excinfo()) return tbh - def __str__(self): - try: - fn = str(self.path) - except py.error.Error: - fn = "???" + def __str__(self) -> str: name = self.frame.code.name try: line = str(self.statement).lstrip() except KeyboardInterrupt: raise - except: # noqa + except BaseException: line = "???" - return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line) + # This output does not quite match Python's repr for traceback entries, + # but changing it to do so would break certain plugins. See + # https://github.com/pytest-dev/pytest/pull/7535/ for details. + return " File %r:%d in %s\n %s\n" % ( + str(self.path), + self.lineno + 1, + name, + line, + ) - def name(self): + @property + def name(self) -> str: + """co_name of underlying code.""" return self.frame.code.raw.co_name - name = property(name, None, None, "co_name of underlaying code") - -class Traceback(list): - """ Traceback objects encapsulate and offer higher level - access to Traceback entries. - """ +class Traceback(List[TracebackEntry]): + """Traceback objects encapsulate and offer higher level access to Traceback entries.""" - Entry = TracebackEntry - - def __init__(self, tb, excinfo=None): - """ initialize from given python traceback object and ExceptionInfo """ + def __init__( + self, + tb: Union[TracebackType, Iterable[TracebackEntry]], + excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, + ) -> None: + """Initialize from given python traceback object and ExceptionInfo.""" self._excinfo = excinfo - if hasattr(tb, "tb_next"): + if isinstance(tb, TracebackType): - def f(cur): - while cur is not None: - yield self.Entry(cur, excinfo=excinfo) - cur = cur.tb_next + def f(cur: TracebackType) -> Iterable[TracebackEntry]: + cur_ = cur # type: Optional[TracebackType] + while cur_ is not None: + yield TracebackEntry(cur_, excinfo=excinfo) + cur_ = cur_.tb_next - list.__init__(self, f(tb)) + super().__init__(f(tb)) else: - list.__init__(self, tb) + super().__init__(tb) - def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None): - """ return a Traceback instance wrapping part of this Traceback - - by provding any combination of path, lineno and firstlineno, the - first frame to start the to-be-returned traceback is determined - - this allows cutting the first part of a Traceback instance e.g. - for formatting reasons (removing some uninteresting bits that deal - with handling of the exception/traceback) + def cut( + self, + path=None, + lineno: Optional[int] = None, + firstlineno: Optional[int] = None, + excludepath: Optional[py.path.local] = None, + ) -> "Traceback": + """Return a Traceback instance wrapping part of this Traceback. + + By providing any combination of path, lineno and firstlineno, the + first frame to start the to-be-returned traceback is determined. + + This allows cutting the first part of a Traceback instance e.g. + for formatting reasons (removing some uninteresting bits that deal + with handling of the exception/traceback). """ for x in self: code = x.frame.code @@ -314,7 +334,7 @@ class Traceback(list): (path is None or codepath == path) and ( excludepath is None - or not hasattr(codepath, "relto") + or not isinstance(codepath, py.path.local) or not codepath.relto(excludepath) ) and (lineno is None or x.lineno == lineno) @@ -323,39 +343,48 @@ class Traceback(list): return Traceback(x._rawentry, self._excinfo) return self - def __getitem__(self, key): - val = super(Traceback, self).__getitem__(key) - if isinstance(key, type(slice(0))): - val = self.__class__(val) - return val + @overload + def __getitem__(self, key: int) -> TracebackEntry: + ... - def filter(self, fn=lambda x: not x.ishidden()): - """ return a Traceback instance with certain items removed + @overload # noqa: F811 + def __getitem__(self, key: slice) -> "Traceback": # noqa: F811 + ... - fn is a function that gets a single argument, a TracebackEntry - instance, and should return True when the item should be added - to the Traceback, False when not + def __getitem__( # noqa: F811 + self, key: Union[int, slice] + ) -> Union[TracebackEntry, "Traceback"]: + if isinstance(key, slice): + return self.__class__(super().__getitem__(key)) + else: + return super().__getitem__(key) - by default this removes all the TracebackEntries which are hidden - (see ishidden() above) + def filter( + self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() + ) -> "Traceback": + """Return a Traceback instance with certain items removed + + fn is a function that gets a single argument, a TracebackEntry + instance, and should return True when the item should be added + to the Traceback, False when not. + + By default this removes all the TracebackEntries which are hidden + (see ishidden() above). """ return Traceback(filter(fn, self), self._excinfo) - def getcrashentry(self): - """ return last non-hidden traceback entry that lead - to the exception of a traceback. - """ + def getcrashentry(self) -> TracebackEntry: + """Return last non-hidden traceback entry that lead to the exception of a traceback.""" for i in range(-1, -len(self) - 1, -1): entry = self[i] if not entry.ishidden(): return entry return self[-1] - def recursionindex(self): - """ return the index of the frame/TracebackEntry where recursion - originates if appropriate, None if no recursion occurred - """ - cache = {} + def recursionindex(self) -> Optional[int]: + """Return the index of the frame/TracebackEntry where recursion originates if + appropriate, None if no recursion occurred.""" + cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] for i, entry in enumerate(self): # id for the code.raw is needed to work around # the strange metaprogramming in the decorator lib from pypi @@ -368,12 +397,10 @@ class Traceback(list): f = entry.frame loc = f.f_locals for otherloc in values: - if f.is_true( - f.eval( - co_equal, - __recursioncache_locals_1=loc, - __recursioncache_locals_2=otherloc, - ) + if f.eval( + co_equal, + __recursioncache_locals_1=loc, + __recursioncache_locals_2=otherloc, ): return i values.append(entry.frame.f_locals) @@ -385,94 +412,136 @@ co_equal = compile( ) +_E = TypeVar("_E", bound=BaseException, covariant=True) + + +@final @attr.s(repr=False) -class ExceptionInfo(object): - """ wraps sys.exc_info() objects and offers - help for navigating the traceback. - """ +class ExceptionInfo(Generic[_E]): + """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" - _assert_start_repr = ( - "AssertionError(u'assert " if _PY2 else "AssertionError('assert " - ) + _assert_start_repr = "AssertionError('assert " - _excinfo = attr.ib() - _striptext = attr.ib(default="") - _traceback = attr.ib(default=None) + _excinfo = attr.ib(type=Optional[Tuple["Type[_E]", "_E", TracebackType]]) + _striptext = attr.ib(type=str, default="") + _traceback = attr.ib(type=Optional[Traceback], default=None) @classmethod - def from_current(cls, exprinfo=None): - """returns an ExceptionInfo matching the current traceback + def from_exc_info( + cls, + exc_info: Tuple["Type[_E]", "_E", TracebackType], + exprinfo: Optional[str] = None, + ) -> "ExceptionInfo[_E]": + """Return an ExceptionInfo for an existing exc_info tuple. .. warning:: Experimental API - - :param exprinfo: a text string helping to determine if we should - strip ``AssertionError`` from the output, defaults - to the exception message/``__str__()`` + :param exprinfo: + A text string helping to determine if we should strip + ``AssertionError`` from the output. Defaults to the exception + message/``__str__()``. """ - tup = sys.exc_info() - assert tup[0] is not None, "no current exception" _striptext = "" - if exprinfo is None and isinstance(tup[1], AssertionError): - exprinfo = getattr(tup[1], "msg", None) + if exprinfo is None and isinstance(exc_info[1], AssertionError): + exprinfo = getattr(exc_info[1], "msg", None) if exprinfo is None: - exprinfo = saferepr(tup[1]) + exprinfo = saferepr(exc_info[1]) if exprinfo and exprinfo.startswith(cls._assert_start_repr): _striptext = "AssertionError: " - return cls(tup, _striptext) + return cls(exc_info, _striptext) @classmethod - def for_later(cls): - """return an unfilled ExceptionInfo + def from_current( + cls, exprinfo: Optional[str] = None + ) -> "ExceptionInfo[BaseException]": + """Return an ExceptionInfo matching the current traceback. + + .. warning:: + + Experimental API + + :param exprinfo: + A text string helping to determine if we should strip + ``AssertionError`` from the output. Defaults to the exception + message/``__str__()``. """ + tup = sys.exc_info() + assert tup[0] is not None, "no current exception" + assert tup[1] is not None, "no current exception" + assert tup[2] is not None, "no current exception" + exc_info = (tup[0], tup[1], tup[2]) + return ExceptionInfo.from_exc_info(exc_info, exprinfo) + + @classmethod + def for_later(cls) -> "ExceptionInfo[_E]": + """Return an unfilled ExceptionInfo.""" return cls(None) + def fill_unfilled(self, exc_info: Tuple["Type[_E]", _E, TracebackType]) -> None: + """Fill an unfilled ExceptionInfo created with ``for_later()``.""" + assert self._excinfo is None, "ExceptionInfo was already filled" + self._excinfo = exc_info + @property - def type(self): - """the exception class""" + def type(self) -> "Type[_E]": + """The exception class.""" + assert ( + self._excinfo is not None + ), ".type can only be used after the context manager exits" return self._excinfo[0] @property - def value(self): - """the exception value""" + def value(self) -> _E: + """The exception value.""" + assert ( + self._excinfo is not None + ), ".value can only be used after the context manager exits" return self._excinfo[1] @property - def tb(self): - """the exception raw traceback""" + def tb(self) -> TracebackType: + """The exception raw traceback.""" + assert ( + self._excinfo is not None + ), ".tb can only be used after the context manager exits" return self._excinfo[2] @property - def typename(self): - """the type name of the exception""" + def typename(self) -> str: + """The type name of the exception.""" + assert ( + self._excinfo is not None + ), ".typename can only be used after the context manager exits" return self.type.__name__ @property - def traceback(self): - """the traceback""" + def traceback(self) -> Traceback: + """The traceback.""" if self._traceback is None: self._traceback = Traceback(self.tb, excinfo=ref(self)) return self._traceback @traceback.setter - def traceback(self, value): + def traceback(self, value: Traceback) -> None: self._traceback = value - def __repr__(self): + def __repr__(self) -> str: if self._excinfo is None: return "<ExceptionInfo for raises contextmanager>" - return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback)) + return "<{} {} tblen={}>".format( + self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback) + ) - def exconly(self, tryshort=False): - """ return the exception as a string + def exconly(self, tryshort: bool = False) -> str: + """Return the exception as a string. - when 'tryshort' resolves to True, and the exception is a - _pytest._code._AssertionError, only the actual exception part of - the exception representation is returned (so 'AssertionError: ' is - removed from the beginning) + When 'tryshort' resolves to True, and the exception is a + _pytest._code._AssertionError, only the actual exception part of + the exception representation is returned (so 'AssertionError: ' is + removed from the beginning). """ lines = format_exception_only(self.type, self.value) text = "".join(lines) @@ -482,11 +551,16 @@ class ExceptionInfo(object): text = text[len(self._striptext) :] return text - def errisinstance(self, exc): - """ return True if the exception is an instance of exc """ + def errisinstance( + self, exc: Union["Type[BaseException]", Tuple["Type[BaseException]", ...]] + ) -> bool: + """Return True if the exception is an instance of exc. + + Consider using ``isinstance(excinfo.value, exc)`` instead. + """ return isinstance(self.value, exc) - def _getreprcrash(self): + def _getreprcrash(self) -> "ReprFileLocation": exconly = self.exconly(tryshort=True) entry = self.traceback.getcrashentry() path, lineno = entry.frame.code.raw.co_filename, entry.lineno @@ -494,22 +568,22 @@ class ExceptionInfo(object): def getrepr( self, - showlocals=False, - style="long", - abspath=False, - tbfilter=True, - funcargs=False, - truncate_locals=True, - chain=True, - ): - """ - Return str()able representation of this exception info. + showlocals: bool = False, + style: "_TracebackStyle" = "long", + abspath: bool = False, + tbfilter: bool = True, + funcargs: bool = False, + truncate_locals: bool = True, + chain: bool = True, + ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: + """Return str()able representation of this exception info. :param bool showlocals: Show locals per traceback entry. Ignored if ``style=="native"``. - :param str style: long|short|no|native traceback style + :param str style: + long|short|no|native|value traceback style. :param bool abspath: If paths should be changed to absolute or left unchanged. @@ -524,7 +598,8 @@ class ExceptionInfo(object): :param bool truncate_locals: With ``showlocals==True``, make sure locals can be safely represented as strings. - :param bool chain: if chained exceptions in Python 3 should be shown. + :param bool chain: + If chained exceptions in Python 3 should be shown. .. versionchanged:: 3.9 @@ -551,89 +626,78 @@ class ExceptionInfo(object): ) return fmt.repr_excinfo(self) - def __str__(self): - if self._excinfo is None: - return repr(self) - entry = self.traceback[-1] - loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) - return str(loc) - - def __unicode__(self): - entry = self.traceback[-1] - loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) - return text_type(loc) + def match(self, regexp: "Union[str, Pattern[str]]") -> "Literal[True]": + """Check whether the regular expression `regexp` matches the string + representation of the exception using :func:`python:re.search`. - def match(self, regexp): - """ - Check whether the regular expression 'regexp' is found in the string - representation of the exception using ``re.search``. If it matches - then True is returned (so that it is possible to write - ``assert excinfo.match()``). If it doesn't match an AssertionError is - raised. + If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True - value = ( - text_type(self.value) if isinstance(regexp, text_type) else str(self.value) - ) - if not re.search(regexp, value): - raise AssertionError( - u"Pattern {!r} not found in {!r}".format(regexp, value) - ) + msg = "Regex pattern {!r} does not match {!r}." + if regexp == str(self.value): + msg += " Did you mean to `re.escape()` the regex?" + assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value)) + # Return True to allow for "assert excinfo.match()". return True @attr.s -class FormattedExcinfo(object): - """ presenting information about failing Functions and Generators. """ +class FormattedExcinfo: + """Presenting information about failing Functions and Generators.""" # for traceback entries flow_marker = ">" fail_marker = "E" - showlocals = attr.ib(default=False) - style = attr.ib(default="long") - abspath = attr.ib(default=True) - tbfilter = attr.ib(default=True) - funcargs = attr.ib(default=False) - truncate_locals = attr.ib(default=True) - chain = attr.ib(default=True) + showlocals = attr.ib(type=bool, default=False) + style = attr.ib(type="_TracebackStyle", default="long") + abspath = attr.ib(type=bool, default=True) + tbfilter = attr.ib(type=bool, default=True) + funcargs = attr.ib(type=bool, default=False) + truncate_locals = attr.ib(type=bool, default=True) + chain = attr.ib(type=bool, default=True) astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) - def _getindent(self, source): - # figure out indent for given source + def _getindent(self, source: "Source") -> int: + # Figure out indent for the given source. try: s = str(source.getstatement(len(source) - 1)) except KeyboardInterrupt: raise - except: # noqa + except BaseException: try: s = str(source[-1]) except KeyboardInterrupt: raise - except: # noqa + except BaseException: return 0 return 4 + (len(s) - len(s.lstrip())) - def _getentrysource(self, entry): + def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: source = entry.getsource(self.astcache) if source is not None: source = source.deindent() return source - def repr_args(self, entry): + def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: if self.funcargs: args = [] for argname, argvalue in entry.frame.getargs(var=True): args.append((argname, saferepr(argvalue))) return ReprFuncArgs(args) + return None - def get_source(self, source, line_index=-1, excinfo=None, short=False): - """ return formatted and marked up source lines. """ - import _pytest._code - + def get_source( + self, + source: Optional["Source"], + line_index: int = -1, + excinfo: Optional[ExceptionInfo[BaseException]] = None, + short: bool = False, + ) -> List[str]: + """Return formatted and marked up source lines.""" lines = [] if source is None or line_index >= len(source.lines): - source = _pytest._code.Source("???") + source = Source("???") line_index = 0 if line_index < 0: line_index += len(source) @@ -651,19 +715,24 @@ class FormattedExcinfo(object): lines.extend(self.get_exconly(excinfo, indent=indent, markall=True)) return lines - def get_exconly(self, excinfo, indent=4, markall=False): + def get_exconly( + self, + excinfo: ExceptionInfo[BaseException], + indent: int = 4, + markall: bool = False, + ) -> List[str]: lines = [] - indent = " " * indent - # get the real exception information out + indentstr = " " * indent + # Get the real exception information out. exlines = excinfo.exconly(tryshort=True).split("\n") - failindent = self.fail_marker + indent[1:] + failindent = self.fail_marker + indentstr[1:] for line in exlines: lines.append(failindent + line) if not markall: - failindent = indent + failindent = indentstr return lines - def repr_locals(self, locals): + def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] @@ -680,31 +749,29 @@ class FormattedExcinfo(object): str_repr = saferepr(value) else: str_repr = safeformat(value) - # if len(str_repr) < 70 or not isinstance(value, - # (list, tuple, dict)): - lines.append("%-10s = %s" % (name, str_repr)) + # if len(str_repr) < 70 or not isinstance(value, (list, tuple, dict)): + lines.append("{:<10} = {}".format(name, str_repr)) # else: # self._line("%-10s =\\" % (name,)) # # XXX # pprint.pprint(value, stream=self.excinfowriter) return ReprLocals(lines) + return None - def repr_traceback_entry(self, entry, excinfo=None): - import _pytest._code - - source = self._getentrysource(entry) - if source is None: - source = _pytest._code.Source("???") - line_index = 0 - else: - # entry.getfirstlinesource() can be -1, should be 0 on jython - line_index = entry.lineno - max(entry.getfirstlinesource(), 0) - - lines = [] - style = entry._repr_style - if style is None: - style = self.style + def repr_traceback_entry( + self, + entry: TracebackEntry, + excinfo: Optional[ExceptionInfo[BaseException]] = None, + ) -> "ReprEntry": + lines = [] # type: List[str] + style = entry._repr_style if entry._repr_style is not None else self.style if style in ("short", "long"): + source = self._getentrysource(entry) + if source is None: + source = Source("???") + line_index = 0 + else: + line_index = entry.lineno - entry.getfirstlinesource() short = style == "short" reprargs = self.repr_args(entry) if not short else None s = self.get_source(source, line_index, excinfo, short=short) @@ -714,14 +781,17 @@ class FormattedExcinfo(object): else: message = excinfo and excinfo.typename or "" path = self._makepath(entry.path) - filelocrepr = ReprFileLocation(path, entry.lineno + 1, message) - localsrepr = None - if not short: - localsrepr = self.repr_locals(entry.locals) - return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style) - if excinfo: - lines.extend(self.get_exconly(excinfo, indent=4)) - return ReprEntry(lines, None, None, None, style) + reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) + localsrepr = self.repr_locals(entry.locals) + return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) + elif style == "value": + if excinfo: + lines.extend(str(excinfo.value).split("\n")) + return ReprEntry(lines, None, None, None, style) + else: + if excinfo: + lines.extend(self.get_exconly(excinfo, indent=4)) + return ReprEntry(lines, None, None, None, style) def _makepath(self, path): if not self.abspath: @@ -733,35 +803,43 @@ class FormattedExcinfo(object): path = np return path - def repr_traceback(self, excinfo): + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": traceback = excinfo.traceback if self.tbfilter: traceback = traceback.filter() - if is_recursion_error(excinfo): + if isinstance(excinfo.value, RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) else: extraline = None last = traceback[-1] entries = [] + if self.style == "value": + reprentry = self.repr_traceback_entry(last, excinfo) + entries.append(reprentry) + return ReprTraceback(entries, None, style=self.style) + for index, entry in enumerate(traceback): einfo = (last == entry) and excinfo or None reprentry = self.repr_traceback_entry(entry, einfo) entries.append(reprentry) return ReprTraceback(entries, extraline, style=self.style) - def _truncate_recursive_traceback(self, traceback): - """ - Truncate the given recursive traceback trying to find the starting point - of the recursion. + def _truncate_recursive_traceback( + self, traceback: Traceback + ) -> Tuple[Traceback, Optional[str]]: + """Truncate the given recursive traceback trying to find the starting + point of the recursion. - The detection is done by going through each traceback entry and finding the - point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``. + The detection is done by going through each traceback entry and + finding the point in which the locals of the frame are equal to the + locals of a previous frame (see ``recursionindex()``). - Handle the situation where the recursion process might raise an exception (for example - comparing numpy arrays using equality raises a TypeError), in which case we do our best to - warn the user of the error and show a limited traceback. + Handle the situation where the recursion process might raise an + exception (for example comparing numpy arrays using equality raises a + TypeError), in which case we do our best to warn the user of the + error and show a limited traceback. """ try: recursionindex = traceback.recursionindex() @@ -774,11 +852,13 @@ class FormattedExcinfo(object): " Displaying first and last {max_frames} stack frames out of {total}." ).format( exc_type=type(e).__name__, - exc_msg=safe_str(e), + exc_msg=str(e), max_frames=max_frames, total=len(traceback), - ) - traceback = traceback[:max_frames] + traceback[-max_frames:] + ) # type: Optional[str] + # Type ignored because adding two instaces of a List subtype + # currently incorrectly has type List instead of the subtype. + traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore else: if recursionindex is not None: extraline = "!!! Recursion detected (same locals & position)" @@ -788,128 +868,136 @@ class FormattedExcinfo(object): return traceback, extraline - def repr_excinfo(self, excinfo): - if _PY2: - reprtraceback = self.repr_traceback(excinfo) - reprcrash = excinfo._getreprcrash() - - return ReprExceptionInfo(reprtraceback, reprcrash) - else: - repr_chain = [] - e = excinfo.value - descr = None - seen = set() - while e is not None and id(e) not in seen: - seen.add(id(e)) - if excinfo: - reprtraceback = self.repr_traceback(excinfo) - reprcrash = excinfo._getreprcrash() - else: - # fallback to native repr if the exception doesn't have a traceback: - # ExceptionInfo objects require a full traceback to work - reprtraceback = ReprTracebackNative( - traceback.format_exception(type(e), e, None) - ) - reprcrash = None - - repr_chain += [(reprtraceback, reprcrash, descr)] - if e.__cause__ is not None and self.chain: - e = e.__cause__ - excinfo = ( - ExceptionInfo((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) - descr = "The above exception was the direct cause of the following exception:" - elif ( - e.__context__ is not None - and not e.__suppress_context__ - and self.chain - ): - e = e.__context__ - excinfo = ( - ExceptionInfo((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) - descr = "During handling of the above exception, another exception occurred:" - else: - e = None - repr_chain.reverse() - return ExceptionChainRepr(repr_chain) - + def repr_excinfo( + self, excinfo: ExceptionInfo[BaseException] + ) -> "ExceptionChainRepr": + repr_chain = ( + [] + ) # type: List[Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]] + e = excinfo.value # type: Optional[BaseException] + excinfo_ = excinfo # type: Optional[ExceptionInfo[BaseException]] + descr = None + seen = set() # type: Set[int] + while e is not None and id(e) not in seen: + seen.add(id(e)) + if excinfo_: + reprtraceback = self.repr_traceback(excinfo_) + reprcrash = ( + excinfo_._getreprcrash() if self.style != "value" else None + ) # type: Optional[ReprFileLocation] + else: + # Fallback to native repr if the exception doesn't have a traceback: + # ExceptionInfo objects require a full traceback to work. + reprtraceback = ReprTracebackNative( + traceback.format_exception(type(e), e, None) + ) + reprcrash = None + + repr_chain += [(reprtraceback, reprcrash, descr)] + if e.__cause__ is not None and self.chain: + e = e.__cause__ + excinfo_ = ( + ExceptionInfo((type(e), e, e.__traceback__)) + if e.__traceback__ + else None + ) + descr = "The above exception was the direct cause of the following exception:" + elif ( + e.__context__ is not None and not e.__suppress_context__ and self.chain + ): + e = e.__context__ + excinfo_ = ( + ExceptionInfo((type(e), e, e.__traceback__)) + if e.__traceback__ + else None + ) + descr = "During handling of the above exception, another exception occurred:" + else: + e = None + repr_chain.reverse() + return ExceptionChainRepr(repr_chain) -class TerminalRepr(object): - def __str__(self): - s = self.__unicode__() - if _PY2: - s = s.encode("utf-8") - return s - def __unicode__(self): +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore +class TerminalRepr: + def __str__(self) -> str: # FYI this is called from pytest-xdist's serialization of exception # information. - io = py.io.TextIO() - tw = py.io.TerminalWriter(file=io) + io = StringIO() + tw = TerminalWriter(file=io) self.toterminal(tw) return io.getvalue().strip() - def __repr__(self): - return "<%s instance at %0x>" % (self.__class__, id(self)) + def __repr__(self) -> str: + return "<{} instance at {:0x}>".format(self.__class__, id(self)) + + def toterminal(self, tw: TerminalWriter) -> None: + raise NotImplementedError() +# This class is abstract -- only subclasses are instantiated. +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ExceptionRepr(TerminalRepr): - def __init__(self): - self.sections = [] + # Provided by subclasses. + reprcrash = None # type: Optional[ReprFileLocation] + reprtraceback = None # type: ReprTraceback + + def __attrs_post_init__(self) -> None: + self.sections = [] # type: List[Tuple[str, str, str]] - def addsection(self, name, content, sep="-"): + def addsection(self, name: str, content: str, sep: str = "-") -> None: self.sections.append((name, content, sep)) - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter) -> None: for name, content, sep in self.sections: tw.sep(sep, name) tw.line(content) +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ExceptionChainRepr(ExceptionRepr): - def __init__(self, chain): - super(ExceptionChainRepr, self).__init__() - self.chain = chain + chain = attr.ib( + type=Sequence[ + Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] + ] + ) + + def __attrs_post_init__(self) -> None: + super().__attrs_post_init__() # reprcrash and reprtraceback of the outermost (the newest) exception - # in the chain - self.reprtraceback = chain[-1][0] - self.reprcrash = chain[-1][1] + # in the chain. + self.reprtraceback = self.chain[-1][0] + self.reprcrash = self.chain[-1][1] - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter) -> None: for element in self.chain: element[0].toterminal(tw) if element[2] is not None: tw.line("") tw.line(element[2], yellow=True) - super(ExceptionChainRepr, self).toterminal(tw) + super().toterminal(tw) +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ReprExceptionInfo(ExceptionRepr): - def __init__(self, reprtraceback, reprcrash): - super(ReprExceptionInfo, self).__init__() - self.reprtraceback = reprtraceback - self.reprcrash = reprcrash + reprtraceback = attr.ib(type="ReprTraceback") + reprcrash = attr.ib(type="ReprFileLocation") - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) - super(ReprExceptionInfo, self).toterminal(tw) + super().toterminal(tw) +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ReprTraceback(TerminalRepr): - entrysep = "_ " + reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]]) + extraline = attr.ib(type=Optional[str]) + style = attr.ib(type="_TracebackStyle") - def __init__(self, reprentries, extraline, style): - self.reprentries = reprentries - self.extraline = extraline - self.style = style + entrysep = "_ " - def toterminal(self, tw): - # the entries might have different styles + def toterminal(self, tw: TerminalWriter) -> None: + # The entries might have different styles. for i, entry in enumerate(self.reprentries): if entry.style == "long": tw.line("") @@ -928,43 +1016,87 @@ class ReprTraceback(TerminalRepr): class ReprTracebackNative(ReprTraceback): - def __init__(self, tblines): + def __init__(self, tblines: Sequence[str]) -> None: self.style = "native" self.reprentries = [ReprEntryNative(tblines)] self.extraline = None +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ReprEntryNative(TerminalRepr): - style = "native" + lines = attr.ib(type=Sequence[str]) + style = "native" # type: _TracebackStyle - def __init__(self, tblines): - self.lines = tblines - - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ReprEntry(TerminalRepr): - def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style): - self.lines = lines - self.reprfuncargs = reprfuncargs - self.reprlocals = reprlocals - self.reprfileloc = filelocrepr - self.style = style - - def toterminal(self, tw): + lines = attr.ib(type=Sequence[str]) + reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"]) + reprlocals = attr.ib(type=Optional["ReprLocals"]) + reprfileloc = attr.ib(type=Optional["ReprFileLocation"]) + style = attr.ib(type="_TracebackStyle") + + def _write_entry_lines(self, tw: TerminalWriter) -> None: + """Write the source code portions of a list of traceback entries with syntax highlighting. + + Usually entries are lines like these: + + " x = 1" + "> assert x == 2" + "E assert 1 == 2" + + This function takes care of rendering the "source" portions of it (the lines without + the "E" prefix) using syntax highlighting, taking care to not highlighting the ">" + character, as doing so might break line continuations. + """ + + if not self.lines: + return + + # separate indents and source lines that are not failures: we want to + # highlight the code but not the indentation, which may contain markers + # such as "> assert 0" + fail_marker = "{} ".format(FormattedExcinfo.fail_marker) + indent_size = len(fail_marker) + indents = [] # type: List[str] + source_lines = [] # type: List[str] + failure_lines = [] # type: List[str] + for index, line in enumerate(self.lines): + is_failure_line = line.startswith(fail_marker) + if is_failure_line: + # from this point on all lines are considered part of the failure + failure_lines.extend(self.lines[index:]) + break + else: + if self.style == "value": + source_lines.append(line) + else: + indents.append(line[:indent_size]) + source_lines.append(line[indent_size:]) + + tw._write_source(source_lines, indents) + + # failure lines are always completely red and bold + for line in failure_lines: + tw.line(line, bold=True, red=True) + + def toterminal(self, tw: TerminalWriter) -> None: if self.style == "short": + assert self.reprfileloc is not None self.reprfileloc.toterminal(tw) - for line in self.lines: - red = line.startswith("E ") - tw.line(line, bold=True, red=red) - # tw.line("") + self._write_entry_lines(tw) + if self.reprlocals: + self.reprlocals.toterminal(tw, indent=" " * 8) return + if self.reprfuncargs: self.reprfuncargs.toterminal(tw) - for line in self.lines: - red = line.startswith("E ") - tw.line(line, bold=True, red=red) + + self._write_entry_lines(tw) + if self.reprlocals: tw.line("") self.reprlocals.toterminal(tw) @@ -973,45 +1105,47 @@ class ReprEntry(TerminalRepr): tw.line("") self.reprfileloc.toterminal(tw) - def __str__(self): - return "%s\n%s\n%s" % ("\n".join(self.lines), self.reprlocals, self.reprfileloc) + def __str__(self) -> str: + return "{}\n{}\n{}".format( + "\n".join(self.lines), self.reprlocals, self.reprfileloc + ) +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ReprFileLocation(TerminalRepr): - def __init__(self, path, lineno, message): - self.path = str(path) - self.lineno = lineno - self.message = message - - def toterminal(self, tw): - # filename and lineno output for each entry, - # using an output format that most editors unterstand + path = attr.ib(type=str, converter=str) + lineno = attr.ib(type=int) + message = attr.ib(type=str) + + def toterminal(self, tw: TerminalWriter) -> None: + # Filename and lineno output for each entry, using an output format + # that most editors understand. msg = self.message i = msg.find("\n") if i != -1: msg = msg[:i] tw.write(self.path, bold=True, red=True) - tw.line(":%s: %s" % (self.lineno, msg)) + tw.line(":{}: {}".format(self.lineno, msg)) +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ReprLocals(TerminalRepr): - def __init__(self, lines): - self.lines = lines + lines = attr.ib(type=Sequence[str]) - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter, indent="") -> None: for line in self.lines: - tw.line(line) + tw.line(indent + line) +@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ReprFuncArgs(TerminalRepr): - def __init__(self, args): - self.args = args + args = attr.ib(type=Sequence[Tuple[str, object]]) - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter) -> None: if self.args: linesofar = "" for name, value in self.args: - ns = "%s = %s" % (safe_str(name), safe_str(value)) + ns = "{} = {}".format(name, value) if len(ns) + len(linesofar) + 2 > tw.fullwidth: if linesofar: tw.line(linesofar) @@ -1026,68 +1160,79 @@ class ReprFuncArgs(TerminalRepr): tw.line("") -def getrawcode(obj, trycall=True): - """ return code object for given function. """ - try: - return obj.__code__ - except AttributeError: - obj = getattr(obj, "im_func", obj) - obj = getattr(obj, "func_code", obj) - obj = getattr(obj, "f_code", obj) - obj = getattr(obj, "__code__", obj) - if trycall and not hasattr(obj, "co_firstlineno"): - if hasattr(obj, "__call__") and not inspect.isclass(obj): - x = getrawcode(obj.__call__, trycall=False) - if hasattr(x, "co_firstlineno"): - return x - return obj - +def getfslineno(obj: object) -> Tuple[Union[str, py.path.local], int]: + """Return source location (path, lineno) for the given object. -if PY35: # RecursionError introduced in 3.5 + If the source cannot be determined return ("", -1). - def is_recursion_error(excinfo): - return excinfo.errisinstance(RecursionError) # noqa + The line number is 0-based. + """ + # xxx let decorators etc specify a sane ordering + # NOTE: this used to be done in _pytest.compat.getfslineno, initially added + # in 6ec13a2b9. It ("place_as") appears to be something very custom. + obj = get_real_func(obj) + if hasattr(obj, "place_as"): + obj = obj.place_as # type: ignore[attr-defined] + try: + code = Code(obj) + except TypeError: + try: + fn = inspect.getsourcefile(obj) or inspect.getfile(obj) # type: ignore[arg-type] + except TypeError: + return "", -1 -else: + fspath = fn and py.path.local(fn) or "" + lineno = -1 + if fspath: + try: + _, lineno = findsource(obj) + except OSError: + pass + return fspath, lineno - def is_recursion_error(excinfo): - if not excinfo.errisinstance(RuntimeError): - return False - try: - return "maximum recursion depth exceeded" in str(excinfo.value) - except UnicodeError: - return False + return code.path, code.firstlineno -# relative paths that we use to filter traceback entries from appearing to the user; -# see filter_traceback +# Relative paths that we use to filter traceback entries from appearing to the user; +# see filter_traceback. # note: if we need to add more paths than what we have now we should probably use a list -# for better maintenance +# for better maintenance. -_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc")) +_PLUGGY_DIR = Path(pluggy.__file__.rstrip("oc")) # pluggy is either a package or a single module depending on the version -if _PLUGGY_DIR.basename == "__init__.py": - _PLUGGY_DIR = _PLUGGY_DIR.dirpath() -_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath() -_PY_DIR = py.path.local(py.__file__).dirpath() +if _PLUGGY_DIR.name == "__init__.py": + _PLUGGY_DIR = _PLUGGY_DIR.parent +_PYTEST_DIR = Path(_pytest.__file__).parent +_PY_DIR = Path(py.__file__).parent + +def filter_traceback(entry: TracebackEntry) -> bool: + """Return True if a TracebackEntry instance should be included in tracebacks. + + We hide traceback entries of: -def filter_traceback(entry): - """Return True if a TracebackEntry instance should be removed from tracebacks: * dynamically generated code (no code to show up for it); * internal traceback from pytest or its internal libraries, py and pluggy. """ # entry.path might sometimes return a str object when the entry - # points to dynamically generated code - # see https://bitbucket.org/pytest-dev/py/issues/71 + # points to dynamically generated code. + # See https://bitbucket.org/pytest-dev/py/issues/71. raw_filename = entry.frame.code.raw.co_filename is_generated = "<" in raw_filename and ">" in raw_filename if is_generated: return False + # entry.path might point to a non-existing file, in which case it will - # also return a str object. see #1133 - p = py.path.local(entry.path) - return ( - not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR) - ) + # also return a str object. See #1133. + p = Path(entry.path) + + parents = p.parents + if _PLUGGY_DIR in parents: + return False + if _PYTEST_DIR in parents: + return False + if _PY_DIR in parents: + return False + + return True diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py index b35e97b9cec..4ba18aa6361 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_code/source.py @@ -1,58 +1,56 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import ast import inspect -import linecache -import sys import textwrap import tokenize import warnings -from ast import PyCF_ONLY_AST as _AST_FLAG from bisect import bisect_right +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + +from _pytest.compat import overload -import py -import six +class Source: + """An immutable object holding a source code fragment. -class Source(object): - """ an immutable object holding a source code fragment, - possibly deindenting it. + When using Source(...), the source lines are deindented. """ - _compilecounter = 0 - - def __init__(self, *parts, **kwargs): - self.lines = lines = [] - de = kwargs.get("deindent", True) - for part in parts: - if not part: - partlines = [] - elif isinstance(part, Source): - partlines = part.lines - elif isinstance(part, (tuple, list)): - partlines = [x.rstrip("\n") for x in part] - elif isinstance(part, six.string_types): - partlines = part.split("\n") - else: - partlines = getsource(part, deindent=de).lines - if de: - partlines = deindent(partlines) - lines.extend(partlines) - - def __eq__(self, other): - try: - return self.lines == other.lines - except AttributeError: - if isinstance(other, str): - return str(self) == other - return False + def __init__(self, obj: object = None) -> None: + if not obj: + self.lines = [] # type: List[str] + elif isinstance(obj, Source): + self.lines = obj.lines + elif isinstance(obj, (tuple, list)): + self.lines = deindent(x.rstrip("\n") for x in obj) + elif isinstance(obj, str): + self.lines = deindent(obj.split("\n")) + else: + rawcode = getrawcode(obj) + src = inspect.getsource(rawcode) + self.lines = deindent(src.split("\n")) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Source): + return NotImplemented + return self.lines == other.lines - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore - def __getitem__(self, key): + @overload + def __getitem__(self, key: int) -> str: + ... + + @overload # noqa: F811 + def __getitem__(self, key: slice) -> "Source": # noqa: F811 + ... + + def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: # noqa: F811 if isinstance(key, int): return self.lines[key] else: @@ -62,13 +60,14 @@ class Source(object): newsource.lines = self.lines[key.start : key.stop] return newsource - def __len__(self): + def __iter__(self) -> Iterator[str]: + return iter(self.lines) + + def __len__(self) -> int: return len(self.lines) - def strip(self): - """ return new source object with trailing - and leading blank lines removed. - """ + def strip(self) -> "Source": + """Return new Source object with trailing and leading blank lines removed.""" start, end = 0, len(self) while start < end and not self.lines[start].strip(): start += 1 @@ -78,161 +77,43 @@ class Source(object): source.lines[:] = self.lines[start:end] return source - def putaround(self, before="", after="", indent=" " * 4): - """ return a copy of the source object with - 'before' and 'after' wrapped around it. - """ - before = Source(before) - after = Source(after) - newsource = Source() - lines = [(indent + line) for line in self.lines] - newsource.lines = before.lines + lines + after.lines - return newsource - - def indent(self, indent=" " * 4): - """ return a copy of the source object with - all lines indented by the given indent-string. - """ + def indent(self, indent: str = " " * 4) -> "Source": + """Return a copy of the source object with all lines indented by the + given indent-string.""" newsource = Source() newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno): - """ return Source statement which contains the - given linenumber (counted from 0). - """ + def getstatement(self, lineno: int) -> "Source": + """Return Source statement which contains the given linenumber + (counted from 0).""" start, end = self.getstatementrange(lineno) return self[start:end] - def getstatementrange(self, lineno): - """ return (start, end) tuple which spans the minimal - statement region which containing the given lineno. - """ + def getstatementrange(self, lineno: int) -> Tuple[int, int]: + """Return (start, end) tuple which spans the minimal statement region + which containing the given lineno.""" if not (0 <= lineno < len(self)): raise IndexError("lineno out of range") ast, start, end = getstatementrange_ast(lineno, self) return start, end - def deindent(self): - """return a new source object deindented.""" + def deindent(self) -> "Source": + """Return a new Source object deindented.""" newsource = Source() newsource.lines[:] = deindent(self.lines) return newsource - def isparseable(self, deindent=True): - """ return True if source is parseable, heuristically - deindenting it by default. - """ - if deindent: - source = str(self.deindent()) - else: - source = str(self) - try: - ast.parse(source) - except (SyntaxError, ValueError, TypeError): - return False - else: - return True - - def __str__(self): + def __str__(self) -> str: return "\n".join(self.lines) - def compile( - self, filename=None, mode="exec", flag=0, dont_inherit=0, _genframe=None - ): - """ return compiled code object. if filename is None - invent an artificial filename which displays - the source/line position of the caller frame. - """ - if not filename or py.path.local(filename).check(file=0): - if _genframe is None: - _genframe = sys._getframe(1) # the caller - fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno - base = "<%d-codegen " % self._compilecounter - self.__class__._compilecounter += 1 - if not filename: - filename = base + "%s:%d>" % (fn, lineno) - else: - filename = base + "%r %s:%d>" % (filename, fn, lineno) - source = "\n".join(self.lines) + "\n" - try: - co = compile(source, filename, mode, flag) - except SyntaxError: - ex = sys.exc_info()[1] - # re-represent syntax errors from parsing python strings - msglines = self.lines[: ex.lineno] - if ex.offset: - msglines.append(" " * ex.offset + "^") - msglines.append("(code was compiled probably from here: %s)" % filename) - newex = SyntaxError("\n".join(msglines)) - newex.offset = ex.offset - newex.lineno = ex.lineno - newex.text = ex.text - raise newex - else: - if flag & _AST_FLAG: - return co - lines = [(x + "\n") for x in self.lines] - linecache.cache[filename] = (1, None, lines, filename) - return co - - -# -# public API shortcut functions -# - - -def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0): - """ compile the given source to a raw code object, - and maintain an internal cache which allows later - retrieval of the source code for the code object - and any recursively created code objects. - """ - if isinstance(source, ast.AST): - # XXX should Source support having AST? - return compile(source, filename, mode, flags, dont_inherit) - _genframe = sys._getframe(1) # the caller - s = Source(source) - co = s.compile(filename, mode, flags, _genframe=_genframe) - return co - - -def getfslineno(obj): - """ Return source location (path, lineno) for the given object. - If the source cannot be determined return ("", -1). - - The line number is 0-based. - """ - from .code import Code - - try: - code = Code(obj) - except TypeError: - try: - fn = inspect.getsourcefile(obj) or inspect.getfile(obj) - except TypeError: - return "", -1 - - fspath = fn and py.path.local(fn) or None - lineno = -1 - if fspath: - try: - _, lineno = findsource(obj) - except IOError: - pass - else: - fspath = code.path - lineno = code.firstlineno - assert isinstance(lineno, int) - return fspath, lineno - # # helper functions # -def findsource(obj): +def findsource(obj) -> Tuple[Optional[Source], int]: try: sourcelines, lineno = inspect.findsource(obj) except Exception: @@ -242,35 +123,36 @@ def findsource(obj): return source, lineno -def getsource(obj, **kwargs): - from .code import getrawcode - - obj = getrawcode(obj) +def getrawcode(obj, trycall: bool = True): + """Return code object for given function.""" try: - strsrc = inspect.getsource(obj) - except IndentationError: - strsrc = '"Buggy python version consider upgrading, cannot get source"' - assert isinstance(strsrc, str) - return Source(strsrc, **kwargs) - - -def deindent(lines): + return obj.__code__ + except AttributeError: + obj = getattr(obj, "f_code", obj) + obj = getattr(obj, "__code__", obj) + if trycall and not hasattr(obj, "co_firstlineno"): + if hasattr(obj, "__call__") and not inspect.isclass(obj): + x = getrawcode(obj.__call__, trycall=False) + if hasattr(x, "co_firstlineno"): + return x + return obj + + +def deindent(lines: Iterable[str]) -> List[str]: return textwrap.dedent("\n".join(lines)).splitlines() -def get_statement_startend2(lineno, node): - import ast - - # flatten all statements and except handlers into one lineno-list - # AST's line numbers start indexing at 1 - values = [] +def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: + # Flatten all statements and except handlers into one lineno-list. + # AST's line numbers start indexing at 1. + values = [] # type: List[int] for x in ast.walk(node): if isinstance(x, (ast.stmt, ast.ExceptHandler)): values.append(x.lineno - 1) for name in ("finalbody", "orelse"): - val = getattr(x, name, None) + val = getattr(x, name, None) # type: Optional[List[ast.stmt]] if val: - # treat the finally/orelse part as its own statement + # Treat the finally/orelse part as its own statement. values.append(val[0].lineno - 1 - 1) values.sort() insert_index = bisect_right(values, lineno) @@ -282,17 +164,22 @@ def get_statement_startend2(lineno, node): return start, end -def getstatementrange_ast(lineno, source, assertion=False, astnode=None): +def getstatementrange_ast( + lineno: int, + source: Source, + assertion: bool = False, + astnode: Optional[ast.AST] = None, +) -> Tuple[ast.AST, int, int]: if astnode is None: content = str(source) # See #4260: - # don't produce duplicate warnings when compiling source to find ast + # Don't produce duplicate warnings when compiling source to find AST. with warnings.catch_warnings(): warnings.simplefilter("ignore") - astnode = compile(content, "source", "exec", _AST_FLAG) + astnode = ast.parse(content, "source", "exec") start, end = get_statement_startend2(lineno, astnode) - # we need to correct the end: + # We need to correct the end: # - ast-parsing strips comments # - there might be empty lines # - we might have lesser indented code blocks at the end @@ -300,10 +187,10 @@ def getstatementrange_ast(lineno, source, assertion=False, astnode=None): end = len(source.lines) if end > start + 1: - # make sure we don't span differently indented code blocks - # by using the BlockFinder helper used which inspect.getsource() uses itself + # Make sure we don't span differently indented code blocks + # by using the BlockFinder helper used which inspect.getsource() uses itself. block_finder = inspect.BlockFinder() - # if we start with an indented line, put blockfinder to "started" mode + # If we start with an indented line, put blockfinder to "started" mode. block_finder.started = source.lines[start][0].isspace() it = ((x + "\n") for x in source.lines[start:end]) try: @@ -314,7 +201,7 @@ def getstatementrange_ast(lineno, source, assertion=False, astnode=None): except Exception: pass - # the end might still point to a comment or empty line, correct it + # The end might still point to a comment or empty line, correct it. while end: line = source.lines[end - 1].lstrip() if line.startswith("#") or not line: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/__init__.py index e69de29bb2d..db001e918cb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/__init__.py @@ -0,0 +1,8 @@ +from .terminalwriter import get_terminal_width +from .terminalwriter import TerminalWriter + + +__all__ = [ + "TerminalWriter", + "get_terminal_width", +] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py index 9b412dccad9..9a4975f61ad 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py @@ -1,83 +1,129 @@ -# -*- coding: utf-8 -*- import pprint +import reprlib +from typing import Any +from typing import Dict +from typing import IO +from typing import Optional -from six.moves import reprlib + +def _try_repr_or_str(obj: object) -> str: + try: + return repr(obj) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException: + return '{}("{}")'.format(type(obj).__name__, obj) -def _call_and_format_exception(call, x, *args): +def _format_repr_exception(exc: BaseException, obj: object) -> str: try: - # Try the vanilla repr and make sure that the result is a string - return call(x, *args) - except Exception as exc: - exc_name = type(exc).__name__ - try: - exc_info = str(exc) - except Exception: - exc_info = "unknown" - return '<[%s("%s") raised in repr()] %s object at 0x%x>' % ( - exc_name, - exc_info, - x.__class__.__name__, - id(x), - ) + exc_info = _try_repr_or_str(exc) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as exc: + exc_info = "unpresentable exception ({})".format(_try_repr_or_str(exc)) + return "<[{} raised in repr()] {} object at 0x{:x}>".format( + exc_info, type(obj).__name__, id(obj) + ) + + +def _ellipsize(s: str, maxsize: int) -> str: + if len(s) > maxsize: + i = max(0, (maxsize - 3) // 2) + j = max(0, maxsize - 3 - i) + return s[:i] + "..." + s[len(s) - j :] + return s class SafeRepr(reprlib.Repr): - """subclass of repr.Repr that limits the resulting size of repr() - and includes information on exceptions raised during the call. - """ + """repr.Repr that limits the resulting size of repr() and includes + information on exceptions raised during the call.""" + + def __init__(self, maxsize: int) -> None: + super().__init__() + self.maxstring = maxsize + self.maxsize = maxsize + + def repr(self, x: object) -> str: + try: + s = super().repr(x) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as exc: + s = _format_repr_exception(exc, x) + return _ellipsize(s, self.maxsize) + + def repr_instance(self, x: object, level: int) -> str: + try: + s = repr(x) + except (KeyboardInterrupt, SystemExit): + raise + except BaseException as exc: + s = _format_repr_exception(exc, x) + return _ellipsize(s, self.maxsize) + + +def safeformat(obj: object) -> str: + """Return a pretty printed string for the given object. - def repr(self, x): - return self._callhelper(reprlib.Repr.repr, self, x) - - def repr_unicode(self, x, level): - # Strictly speaking wrong on narrow builds - def repr(u): - if "'" not in u: - return u"'%s'" % u - elif '"' not in u: - return u'"%s"' % u - else: - return u"'%s'" % u.replace("'", r"\'") - - s = repr(x[: self.maxstring]) - if len(s) > self.maxstring: - i = max(0, (self.maxstring - 3) // 2) - j = max(0, self.maxstring - 3 - i) - s = repr(x[:i] + x[len(x) - j :]) - s = s[:i] + "..." + s[len(s) - j :] - return s - - def repr_instance(self, x, level): - return self._callhelper(repr, x) - - def _callhelper(self, call, x, *args): - s = _call_and_format_exception(call, x, *args) - if len(s) > self.maxsize: - i = max(0, (self.maxsize - 3) // 2) - j = max(0, self.maxsize - 3 - i) - s = s[:i] + "..." + s[len(s) - j :] - return s - - -def safeformat(obj): - """return a pretty printed string for the given object. Failing __repr__ functions of user instances will be represented with a short exception info. """ - return _call_and_format_exception(pprint.pformat, obj) + try: + return pprint.pformat(obj) + except Exception as exc: + return _format_repr_exception(exc, obj) -def saferepr(obj, maxsize=240): - """return a size-limited safe repr-string for the given object. +def saferepr(obj: object, maxsize: int = 240) -> str: + """Return a size-limited safe repr-string for the given object. + Failing __repr__ functions of user instances will be represented with a short exception info and 'saferepr' generally takes - care to never raise exceptions itself. This function is a wrapper - around the Repr/reprlib functionality of the standard 2.6 lib. + care to never raise exceptions itself. + + This function is a wrapper around the Repr/reprlib functionality of the + standard 2.6 lib. """ - # review exception handling - srepr = SafeRepr() - srepr.maxstring = maxsize - srepr.maxsize = maxsize - srepr.maxother = 160 - return srepr.repr(obj) + return SafeRepr(maxsize).repr(obj) + + +class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter): + """PrettyPrinter that always dispatches (regardless of width).""" + + def _format( + self, + object: object, + stream: IO[str], + indent: int, + allowance: int, + context: Dict[int, Any], + level: int, + ) -> None: + # Type ignored because _dispatch is private. + p = self._dispatch.get(type(object).__repr__, None) # type: ignore[attr-defined] + + objid = id(object) + if objid in context or p is None: + # Type ignored because _format is private. + super()._format( # type: ignore[misc] + object, stream, indent, allowance, context, level, + ) + return + + context[objid] = 1 + p(self, object, stream, indent, allowance, context, level + 1) + del context[objid] + + +def _pformat_dispatch( + object: object, + indent: int = 1, + width: int = 80, + depth: Optional[int] = None, + *, + compact: bool = False +) -> str: + return AlwaysDispatchingPrettyPrinter( + indent=indent, width=width, depth=depth, compact=compact + ).pformat(object) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py new file mode 100644 index 00000000000..a9404ebcc16 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py @@ -0,0 +1,210 @@ +"""Helper functions for writing to terminals and files.""" +import os +import shutil +import sys +from typing import Optional +from typing import Sequence +from typing import TextIO + +from .wcwidth import wcswidth +from _pytest.compat import final + + +# This code was initially copied from py 1.8.1, file _io/terminalwriter.py. + + +def get_terminal_width() -> int: + width, _ = shutil.get_terminal_size(fallback=(80, 24)) + + # The Windows get_terminal_size may be bogus, let's sanify a bit. + if width < 40: + width = 80 + + return width + + +def should_do_markup(file: TextIO) -> bool: + if os.environ.get("PY_COLORS") == "1": + return True + if os.environ.get("PY_COLORS") == "0": + return False + if "NO_COLOR" in os.environ: + return False + if "FORCE_COLOR" in os.environ: + return True + return ( + hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb" + ) + + +@final +class TerminalWriter: + _esctable = dict( + black=30, + red=31, + green=32, + yellow=33, + blue=34, + purple=35, + cyan=36, + white=37, + Black=40, + Red=41, + Green=42, + Yellow=43, + Blue=44, + Purple=45, + Cyan=46, + White=47, + bold=1, + light=2, + blink=5, + invert=7, + ) + + def __init__(self, file: Optional[TextIO] = None) -> None: + if file is None: + file = sys.stdout + if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": + try: + import colorama + except ImportError: + pass + else: + file = colorama.AnsiToWin32(file).stream + assert file is not None + self._file = file + self.hasmarkup = should_do_markup(file) + self._current_line = "" + self._terminal_width = None # type: Optional[int] + self.code_highlight = True + + @property + def fullwidth(self) -> int: + if self._terminal_width is not None: + return self._terminal_width + return get_terminal_width() + + @fullwidth.setter + def fullwidth(self, value: int) -> None: + self._terminal_width = value + + @property + def width_of_current_line(self) -> int: + """Return an estimate of the width so far in the current line.""" + return wcswidth(self._current_line) + + def markup(self, text: str, **markup: bool) -> str: + for name in markup: + if name not in self._esctable: + raise ValueError("unknown markup: {!r}".format(name)) + if self.hasmarkup: + esc = [self._esctable[name] for name, on in markup.items() if on] + if esc: + text = "".join("\x1b[%sm" % cod for cod in esc) + text + "\x1b[0m" + return text + + def sep( + self, + sepchar: str, + title: Optional[str] = None, + fullwidth: Optional[int] = None, + **markup: bool + ) -> None: + if fullwidth is None: + fullwidth = self.fullwidth + # The goal is to have the line be as long as possible + # under the condition that len(line) <= fullwidth. + if sys.platform == "win32": + # If we print in the last column on windows we are on a + # new line but there is no way to verify/neutralize this + # (we may not know the exact line width). + # So let's be defensive to avoid empty lines in the output. + fullwidth -= 1 + if title is not None: + # we want 2 + 2*len(fill) + len(title) <= fullwidth + # i.e. 2 + 2*len(sepchar)*N + len(title) <= fullwidth + # 2*len(sepchar)*N <= fullwidth - len(title) - 2 + # N <= (fullwidth - len(title) - 2) // (2*len(sepchar)) + N = max((fullwidth - len(title) - 2) // (2 * len(sepchar)), 1) + fill = sepchar * N + line = "{} {} {}".format(fill, title, fill) + else: + # we want len(sepchar)*N <= fullwidth + # i.e. N <= fullwidth // len(sepchar) + line = sepchar * (fullwidth // len(sepchar)) + # In some situations there is room for an extra sepchar at the right, + # in particular if we consider that with a sepchar like "_ " the + # trailing space is not important at the end of the line. + if len(line) + len(sepchar.rstrip()) <= fullwidth: + line += sepchar.rstrip() + + self.line(line, **markup) + + def write(self, msg: str, *, flush: bool = False, **markup: bool) -> None: + if msg: + current_line = msg.rsplit("\n", 1)[-1] + if "\n" in msg: + self._current_line = current_line + else: + self._current_line += current_line + + msg = self.markup(msg, **markup) + + try: + self._file.write(msg) + except UnicodeEncodeError: + # Some environments don't support printing general Unicode + # strings, due to misconfiguration or otherwise; in that case, + # print the string escaped to ASCII. + # When the Unicode situation improves we should consider + # letting the error propagate instead of masking it (see #7475 + # for one brief attempt). + msg = msg.encode("unicode-escape").decode("ascii") + self._file.write(msg) + + if flush: + self.flush() + + def line(self, s: str = "", **markup: bool) -> None: + self.write(s, **markup) + self.write("\n") + + def flush(self) -> None: + self._file.flush() + + def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> None: + """Write lines of source code possibly highlighted. + + Keeping this private for now because the API is clunky. We should discuss how + to evolve the terminal writer so we can have more precise color support, for example + being able to write part of a line in one color and the rest in another, and so on. + """ + if indents and len(indents) != len(lines): + raise ValueError( + "indents size ({}) should have same size as lines ({})".format( + len(indents), len(lines) + ) + ) + if not indents: + indents = [""] * len(lines) + source = "\n".join(lines) + new_lines = self._highlight(source).splitlines() + for indent, new_line in zip(indents, new_lines): + self.line(indent + new_line) + + def _highlight(self, source: str) -> str: + """Highlight the given source code if we have markup support.""" + if not self.hasmarkup or not self.code_highlight: + return source + try: + from pygments.formatters.terminal import TerminalFormatter + from pygments.lexers.python import PythonLexer + from pygments import highlight + except ImportError: + return source + else: + highlighted = highlight( + source, PythonLexer(), TerminalFormatter(bg="dark") + ) # type: str + return highlighted diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py new file mode 100644 index 00000000000..e5c7bf4d868 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py @@ -0,0 +1,55 @@ +import unicodedata +from functools import lru_cache + + +@lru_cache(100) +def wcwidth(c: str) -> int: + """Determine how many columns are needed to display a character in a terminal. + + Returns -1 if the character is not printable. + Returns 0, 1 or 2 for other characters. + """ + o = ord(c) + + # ASCII fast path. + if 0x20 <= o < 0x07F: + return 1 + + # Some Cf/Zp/Zl characters which should be zero-width. + if ( + o == 0x0000 + or 0x200B <= o <= 0x200F + or 0x2028 <= o <= 0x202E + or 0x2060 <= o <= 0x2063 + ): + return 0 + + category = unicodedata.category(c) + + # Control characters. + if category == "Cc": + return -1 + + # Combining characters with zero width. + if category in ("Me", "Mn"): + return 0 + + # Full/Wide east asian characters. + if unicodedata.east_asian_width(c) in ("F", "W"): + return 2 + + return 1 + + +def wcswidth(s: str) -> int: + """Determine how many columns are needed to display a string in a terminal. + + Returns -1 if the string contains non-printable characters. + """ + width = 0 + for c in unicodedata.normalize("NFC", s): + wc = wcwidth(c) + if wc < 0: + return -1 + width += wc + return width diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py new file mode 100644 index 00000000000..04ffab6589f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/_version.py @@ -0,0 +1,4 @@ +# coding: utf-8 +# file generated by setuptools_scm +# don't change, don't track in version control +version = '6.1.1' diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py index 6b6abb863a3..06057d0c4c1 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py @@ -1,21 +1,25 @@ -# -*- coding: utf-8 -*- -""" -support for presenting detailed information in failing assertions. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Support for presenting detailed information in failing assertions.""" import sys - -import six +from typing import Any +from typing import Generator +from typing import List +from typing import Optional from _pytest.assertion import rewrite from _pytest.assertion import truncate from _pytest.assertion import util +from _pytest.assertion.rewrite import assertstate_key +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config import hookimpl +from _pytest.config.argparsing import Parser +from _pytest.nodes import Item + +if TYPE_CHECKING: + from _pytest.main import Session -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--assert", @@ -24,15 +28,23 @@ def pytest_addoption(parser): choices=("rewrite", "plain"), default="rewrite", metavar="MODE", - help="""Control assertion debugging tools. 'plain' - performs no assertion debugging. 'rewrite' - (the default) rewrites assert statements in - test modules on import to provide assert - expression information.""", + help=( + "Control assertion debugging tools.\n" + "'plain' performs no assertion debugging.\n" + "'rewrite' (the default) rewrites assert statements in test modules" + " on import to provide assert expression information." + ), + ) + parser.addini( + "enable_assertion_pass_hook", + type="bool", + default=False, + help="Enables the pytest_assertion_pass hook." + "Make sure to delete any previously generated pyc cache files.", ) -def register_assert_rewrite(*names): +def register_assert_rewrite(*names: str) -> None: """Register one or more module names to be rewritten on import. This function will make sure that this module or all modules inside @@ -41,50 +53,48 @@ def register_assert_rewrite(*names): actually imported, usually in your __init__.py if you are a plugin using a package. - :raise TypeError: if the given module names are not strings. + :raises TypeError: If the given module names are not strings. """ for name in names: if not isinstance(name, str): - msg = "expected module names as *args, got {0} instead" + msg = "expected module names as *args, got {0} instead" # type: ignore[unreachable] raise TypeError(msg.format(repr(names))) for hook in sys.meta_path: if isinstance(hook, rewrite.AssertionRewritingHook): importhook = hook break else: - importhook = DummyRewriteHook() + # TODO(typing): Add a protocol for mark_rewrite() and use it + # for importhook and for PytestPluginManager.rewrite_hook. + importhook = DummyRewriteHook() # type: ignore importhook.mark_rewrite(*names) -class DummyRewriteHook(object): +class DummyRewriteHook: """A no-op import hook for when rewriting is disabled.""" - def mark_rewrite(self, *names): + def mark_rewrite(self, *names: str) -> None: pass -class AssertionState(object): +class AssertionState: """State for the assertion plugin.""" - def __init__(self, config, mode): + def __init__(self, config: Config, mode) -> None: self.mode = mode self.trace = config.trace.root.get("assertion") - self.hook = None + self.hook = None # type: Optional[rewrite.AssertionRewritingHook] -def install_importhook(config): +def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: """Try to install the rewrite hook, raise SystemError if it fails.""" - # Jython has an AST bug that make the assertion rewriting hook malfunction. - if sys.platform.startswith("java"): - raise SystemError("rewrite not supported") - - config._assertstate = AssertionState(config, "rewrite") - config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config) + config._store[assertstate_key] = AssertionState(config, "rewrite") + config._store[assertstate_key].hook = hook = rewrite.AssertionRewritingHook(config) sys.meta_path.insert(0, hook) - config._assertstate.trace("installed rewrite import hook") + config._store[assertstate_key].trace("installed rewrite import hook") - def undo(): - hook = config._assertstate.hook + def undo() -> None: + hook = config._store[assertstate_key].hook if hook is not None and hook in sys.meta_path: sys.meta_path.remove(hook) @@ -92,27 +102,29 @@ def install_importhook(config): return hook -def pytest_collection(session): - # this hook is only called when test modules are collected +def pytest_collection(session: "Session") -> None: + # This hook is only called when test modules are collected # so for example not in the master process of pytest-xdist - # (which does not collect test modules) - assertstate = getattr(session.config, "_assertstate", None) + # (which does not collect test modules). + assertstate = session.config._store.get(assertstate_key, None) if assertstate: if assertstate.hook is not None: assertstate.hook.set_session(session) -def pytest_runtest_setup(item): - """Setup the pytest_assertrepr_compare hook +@hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: + """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks. - The newinterpret and rewrite modules will use util._reprcompare if - it exists to use custom reporting via the - pytest_assertrepr_compare hook. This sets up this custom + The rewrite module will use util._reprcompare if it exists to use custom + reporting via the pytest_assertrepr_compare hook. This sets up this custom comparison for the test. """ - def callbinrepr(op, left, right): - """Call the pytest_assertrepr_compare hook and prepare the result + ihook = item.ihook + + def callbinrepr(op, left: object, right: object) -> Optional[str]: + """Call the pytest_assertrepr_compare hook and prepare the result. This uses the first result from the hook and then ensures the following: @@ -126,31 +138,42 @@ def pytest_runtest_setup(item): The result can be formatted by util.format_explanation() for pretty printing. """ - hook_result = item.ihook.pytest_assertrepr_compare( + hook_result = ihook.pytest_assertrepr_compare( config=item.config, op=op, left=left, right=right ) for new_expl in hook_result: if new_expl: new_expl = truncate.truncate_if_required(new_expl, item) new_expl = [line.replace("\n", "\\n") for line in new_expl] - res = six.text_type("\n~").join(new_expl) + res = "\n~".join(new_expl) if item.config.getvalue("assertmode") == "rewrite": res = res.replace("%", "%%") return res + return None + saved_assert_hooks = util._reprcompare, util._assertion_pass util._reprcompare = callbinrepr + if ihook.pytest_assertion_pass.get_hookimpls(): + + def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: + ihook.pytest_assertion_pass(item=item, lineno=lineno, orig=orig, expl=expl) + + util._assertion_pass = call_assertion_pass_hook + + yield -def pytest_runtest_teardown(item): - util._reprcompare = None + util._reprcompare, util._assertion_pass = saved_assert_hooks -def pytest_sessionfinish(session): - assertstate = getattr(session.config, "_assertstate", None) +def pytest_sessionfinish(session: "Session") -> None: + assertstate = session.config._store.get(assertstate_key, None) if assertstate: if assertstate.hook is not None: assertstate.hook.set_session(None) -# Expose this plugin's implementation for the pytest_assertrepr_compare hook -pytest_assertrepr_compare = util.assertrepr_compare +def pytest_assertrepr_compare( + config: Config, op: str, left: Any, right: Any +) -> Optional[List[str]]: + return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py index 1c6161b212d..5ff57824579 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py @@ -1,134 +1,140 @@ -# -*- coding: utf-8 -*- -"""Rewrite assertion AST to produce nice error messages""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Rewrite assertion AST to produce nice error messages.""" import ast import errno -import imp +import functools +import importlib.abc +import importlib.machinery +import importlib.util +import io import itertools import marshal import os -import re -import string import struct import sys +import tokenize import types +from typing import Callable +from typing import Dict +from typing import IO +from typing import Iterable +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Union -import atomicwrites import py -import six from _pytest._io.saferepr import saferepr +from _pytest._version import version from _pytest.assertion import util from _pytest.assertion.util import ( # noqa: F401 format_explanation as _format_explanation, ) -from _pytest.compat import spec_from_file_location +from _pytest.compat import fspath +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.main import Session from _pytest.pathlib import fnmatch_ex +from _pytest.pathlib import Path from _pytest.pathlib import PurePath +from _pytest.store import StoreKey -# pytest caches rewritten pycs in __pycache__. -if hasattr(imp, "get_tag"): - PYTEST_TAG = imp.get_tag() + "-PYTEST" -else: - if hasattr(sys, "pypy_version_info"): - impl = "pypy" - elif sys.platform == "java": - impl = "jython" - else: - impl = "cpython" - ver = sys.version_info - PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1]) - del ver, impl +if TYPE_CHECKING: + from _pytest.assertion import AssertionState # noqa: F401 -PYC_EXT = ".py" + (__debug__ and "c" or "o") -PYC_TAIL = "." + PYTEST_TAG + PYC_EXT -ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3 +assertstate_key = StoreKey["AssertionState"]() -if sys.version_info >= (3, 5): - ast_Call = ast.Call -else: - def ast_Call(a, b, c): - return ast.Call(a, b, c, None, None) +# pytest caches rewritten pycs in pycache dirs +PYTEST_TAG = "{}-pytest-{}".format(sys.implementation.cache_tag, version) +PYC_EXT = ".py" + (__debug__ and "c" or "o") +PYC_TAIL = "." + PYTEST_TAG + PYC_EXT -class AssertionRewritingHook(object): - """PEP302 Import hook which rewrites asserts.""" +class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader): + """PEP302/PEP451 import hook which rewrites asserts.""" - def __init__(self, config): + def __init__(self, config: Config) -> None: self.config = config try: self.fnpats = config.getini("python_files") except ValueError: self.fnpats = ["test_*.py", "*_test.py"] - self.session = None - self.modules = {} - self._rewritten_names = set() - self._must_rewrite = set() + self.session = None # type: Optional[Session] + self._rewritten_names = set() # type: Set[str] + self._must_rewrite = set() # type: Set[str] # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, # which might result in infinite recursion (#3506) self._writing_pyc = False self._basenames_to_check_rewrite = {"conftest"} - self._marked_for_rewrite_cache = {} + self._marked_for_rewrite_cache = {} # type: Dict[str, bool] self._session_paths_checked = False - def set_session(self, session): + def set_session(self, session: Optional[Session]) -> None: self.session = session self._session_paths_checked = False - def _imp_find_module(self, name, path=None): - """Indirection so we can mock calls to find_module originated from the hook during testing""" - return imp.find_module(name, path) + # Indirection so we can mock calls to find_spec originated from the hook during testing + _find_spec = importlib.machinery.PathFinder.find_spec - def find_module(self, name, path=None): + def find_spec( + self, + name: str, + path: Optional[Sequence[Union[str, bytes]]] = None, + target: Optional[types.ModuleType] = None, + ) -> Optional[importlib.machinery.ModuleSpec]: if self._writing_pyc: return None - state = self.config._assertstate + state = self.config._store[assertstate_key] if self._early_rewrite_bailout(name, state): return None state.trace("find_module called for: %s" % name) - names = name.rsplit(".", 1) - lastname = names[-1] - pth = None - if path is not None: - # Starting with Python 3.3, path is a _NamespacePath(), which - # causes problems if not converted to list. - path = list(path) - if len(path) == 1: - pth = path[0] - if pth is None: - try: - fd, fn, desc = self._imp_find_module(lastname, path) - except ImportError: - return None - if fd is not None: - fd.close() - tp = desc[2] - if tp == imp.PY_COMPILED: - if hasattr(imp, "source_from_cache"): - try: - fn = imp.source_from_cache(fn) - except ValueError: - # Python 3 doesn't like orphaned but still-importable - # .pyc files. - fn = fn[:-1] - else: - fn = fn[:-1] - elif tp != imp.PY_SOURCE: - # Don't know what this is. - return None + + # Type ignored because mypy is confused about the `self` binding here. + spec = self._find_spec(name, path) # type: ignore + if ( + # the import machinery could not find a file to import + spec is None + # this is a namespace package (without `__init__.py`) + # there's nothing to rewrite there + # python3.5 - python3.6: `namespace` + # python3.7+: `None` + or spec.origin == "namespace" + or spec.origin is None + # we can only rewrite source files + or not isinstance(spec.loader, importlib.machinery.SourceFileLoader) + # if the file doesn't exist, we can't rewrite it + or not os.path.exists(spec.origin) + ): + return None else: - fn = os.path.join(pth, name.rpartition(".")[2] + ".py") + fn = spec.origin - fn_pypath = py.path.local(fn) - if not self._should_rewrite(name, fn_pypath, state): + if not self._should_rewrite(name, fn, state): return None - self._rewritten_names.add(name) + return importlib.util.spec_from_file_location( + name, + fn, + loader=self, + submodule_search_locations=spec.submodule_search_locations, + ) + + def create_module( + self, spec: importlib.machinery.ModuleSpec + ) -> Optional[types.ModuleType]: + return None # default behaviour is fine + + def exec_module(self, module: types.ModuleType) -> None: + assert module.__spec__ is not None + assert module.__spec__.origin is not None + fn = Path(module.__spec__.origin) + state = self.config._store[assertstate_key] + + self._rewritten_names.add(module.__name__) # The requested module looks like a test file, so rewrite it. This is # the most magical part of the process: load the source, rewrite the @@ -139,37 +145,21 @@ class AssertionRewritingHook(object): # cached pyc is always a complete, valid pyc. Operations on it must be # atomic. POSIX's atomic rename comes in handy. write = not sys.dont_write_bytecode - cache_dir = os.path.join(fn_pypath.dirname, "__pycache__") + cache_dir = get_cache_dir(fn) if write: - try: - os.mkdir(cache_dir) - except OSError: - e = sys.exc_info()[1].errno - if e == errno.EEXIST: - # Either the __pycache__ directory already exists (the - # common case) or it's blocked by a non-dir node. In the - # latter case, we'll ignore it in _write_pyc. - pass - elif e in [errno.ENOENT, errno.ENOTDIR]: - # One of the path components was not a directory, likely - # because we're in a zip file. - write = False - elif e in [errno.EACCES, errno.EROFS, errno.EPERM]: - state.trace("read only directory: %r" % fn_pypath.dirname) - write = False - else: - raise - cache_name = fn_pypath.basename[:-3] + PYC_TAIL - pyc = os.path.join(cache_dir, cache_name) + ok = try_makedirs(cache_dir) + if not ok: + write = False + state.trace("read only directory: {}".format(cache_dir)) + + cache_name = fn.name[:-3] + PYC_TAIL + pyc = cache_dir / cache_name # Notice that even if we're in a read-only directory, I'm going # to check for a cached pyc. This may not be optimal... - co = _read_pyc(fn_pypath, pyc, state.trace) + co = _read_pyc(fn, pyc, state.trace) if co is None: - state.trace("rewriting %r" % (fn,)) - source_stat, co = _rewrite_test(self.config, fn_pypath) - if co is None: - # Probably a SyntaxError in the test. - return None + state.trace("rewriting {!r}".format(fn)) + source_stat, co = _rewrite_test(fn, self.config) if write: self._writing_pyc = True try: @@ -177,23 +167,23 @@ class AssertionRewritingHook(object): finally: self._writing_pyc = False else: - state.trace("found cached rewritten pyc for %r" % (fn,)) - self.modules[name] = co, pyc - return self + state.trace("found cached rewritten pyc for {}".format(fn)) + exec(co, module.__dict__) - def _early_rewrite_bailout(self, name, state): - """ - This is a fast way to get out of rewriting modules. Profiling has - shown that the call to imp.find_module (inside of the find_module - from this class) is a major slowdown, so, this method tries to - filter what we're sure won't be rewritten before getting to it. + def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: + """A fast way to get out of rewriting modules. + + Profiling has shown that the call to PathFinder.find_spec (inside of + the find_spec from this class) is a major slowdown, so, this method + tries to filter what we're sure won't be rewritten before getting to + it. """ if self.session is not None and not self._session_paths_checked: self._session_paths_checked = True - for path in self.session._initialpaths: + for initial_path in self.session._initialpaths: # Make something as c:/projects/my_project/path.py -> # ['c:', 'projects', 'my_project', 'path.py'] - parts = str(path).split(os.path.sep) + parts = str(initial_path).split(os.path.sep) # add 'path' to basenames to be checked. self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0]) @@ -216,44 +206,48 @@ class AssertionRewritingHook(object): if self._is_marked_for_rewrite(name, state): return False - state.trace("early skip of rewriting module: %s" % (name,)) + state.trace("early skip of rewriting module: {}".format(name)) return True - def _should_rewrite(self, name, fn_pypath, state): + def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: # always rewrite conftest files - fn = str(fn_pypath) - if fn_pypath.basename == "conftest.py": - state.trace("rewriting conftest file: %r" % (fn,)) + if os.path.basename(fn) == "conftest.py": + state.trace("rewriting conftest file: {!r}".format(fn)) return True if self.session is not None: - if self.session.isinitpath(fn): - state.trace("matched test file (was specified on cmdline): %r" % (fn,)) + if self.session.isinitpath(py.path.local(fn)): + state.trace( + "matched test file (was specified on cmdline): {!r}".format(fn) + ) return True # modules not passed explicitly on the command line are only # rewritten if they match the naming convention for test files + fn_path = PurePath(fn) for pat in self.fnpats: - if fn_pypath.fnmatch(pat): - state.trace("matched test file %r" % (fn,)) + if fnmatch_ex(pat, fn_path): + state.trace("matched test file {!r}".format(fn)) return True return self._is_marked_for_rewrite(name, state) - def _is_marked_for_rewrite(self, name, state): + def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool: try: return self._marked_for_rewrite_cache[name] except KeyError: for marked in self._must_rewrite: if name == marked or name.startswith(marked + "."): - state.trace("matched marked file %r (from %r)" % (name, marked)) + state.trace( + "matched marked file {!r} (from {!r})".format(name, marked) + ) self._marked_for_rewrite_cache[name] = True return True self._marked_for_rewrite_cache[name] = False return False - def mark_rewrite(self, *names): + def mark_rewrite(self, *names: str) -> None: """Mark import names as needing to be rewritten. The named module or package as well as any nested modules will @@ -263,176 +257,133 @@ class AssertionRewritingHook(object): set(names).intersection(sys.modules).difference(self._rewritten_names) ) for name in already_imported: + mod = sys.modules[name] if not AssertionRewriter.is_rewrite_disabled( - sys.modules[name].__doc__ or "" - ): + mod.__doc__ or "" + ) and not isinstance(mod.__loader__, type(self)): self._warn_already_imported(name) self._must_rewrite.update(names) self._marked_for_rewrite_cache.clear() - def _warn_already_imported(self, name): + def _warn_already_imported(self, name: str) -> None: from _pytest.warning_types import PytestAssertRewriteWarning - from _pytest.warnings import _issue_warning_captured - _issue_warning_captured( + self.config.issue_config_time_warning( PytestAssertRewriteWarning( "Module already imported so cannot be rewritten: %s" % name ), - self.config.hook, stacklevel=5, ) - def load_module(self, name): - co, pyc = self.modules.pop(name) - if name in sys.modules: - # If there is an existing module object named 'fullname' in - # sys.modules, the loader must use that existing module. (Otherwise, - # the reload() builtin will not work correctly.) - mod = sys.modules[name] - else: - # I wish I could just call imp.load_compiled here, but __file__ has to - # be set properly. In Python 3.2+, this all would be handled correctly - # by load_compiled. - mod = sys.modules[name] = imp.new_module(name) - try: - mod.__file__ = co.co_filename - # Normally, this attribute is 3.2+. - mod.__cached__ = pyc - mod.__loader__ = self - # Normally, this attribute is 3.4+ - mod.__spec__ = spec_from_file_location(name, co.co_filename, loader=self) - exec(co, mod.__dict__) - except: # noqa - if name in sys.modules: - del sys.modules[name] - raise - return sys.modules[name] - - def is_package(self, name): - try: - fd, fn, desc = self._imp_find_module(name) - except ImportError: - return False - if fd is not None: - fd.close() - tp = desc[2] - return tp == imp.PKG_DIRECTORY - - def get_data(self, pathname): - """Optional PEP302 get_data API. - """ + def get_data(self, pathname: Union[str, bytes]) -> bytes: + """Optional PEP302 get_data API.""" with open(pathname, "rb") as f: return f.read() -def _write_pyc(state, co, source_stat, pyc): +def _write_pyc_fp( + fp: IO[bytes], source_stat: os.stat_result, co: types.CodeType +) -> None: # Technically, we don't have to have the same pyc format as # (C)Python, since these "pycs" should never be seen by builtin - # import. However, there's little reason deviate, and I hope - # sometime to be able to use imp.load_compiled to load them. (See - # the comment in load_module above.) - try: - with atomicwrites.atomic_write(pyc, mode="wb", overwrite=True) as fp: - fp.write(imp.get_magic()) - # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903) - mtime = int(source_stat.mtime) & 0xFFFFFFFF - size = source_stat.size & 0xFFFFFFFF - # "<LL" stands for 2 unsigned longs, little-ending - fp.write(struct.pack("<LL", mtime, size)) - fp.write(marshal.dumps(co)) - except EnvironmentError as e: - state.trace("error writing pyc file at %s: errno=%s" % (pyc, e.errno)) - # we ignore any failure to write the cache file - # there are many reasons, permission-denied, __pycache__ being a - # file etc. - return False - return True + # import. However, there's little reason deviate. + fp.write(importlib.util.MAGIC_NUMBER) + # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903) + mtime = int(source_stat.st_mtime) & 0xFFFFFFFF + size = source_stat.st_size & 0xFFFFFFFF + # "<LL" stands for 2 unsigned longs, little-ending + fp.write(struct.pack("<LL", mtime, size)) + fp.write(marshal.dumps(co)) + + +if sys.platform == "win32": + from atomicwrites import atomic_write + + def _write_pyc( + state: "AssertionState", + co: types.CodeType, + source_stat: os.stat_result, + pyc: Path, + ) -> bool: + try: + with atomic_write(fspath(pyc), mode="wb", overwrite=True) as fp: + _write_pyc_fp(fp, source_stat, co) + except OSError as e: + state.trace("error writing pyc file at {}: {}".format(pyc, e)) + # we ignore any failure to write the cache file + # there are many reasons, permission-denied, pycache dir being a + # file etc. + return False + return True -RN = "\r\n".encode("utf-8") -N = "\n".encode("utf-8") +else: -cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+") -BOM_UTF8 = "\xef\xbb\xbf" + def _write_pyc( + state: "AssertionState", + co: types.CodeType, + source_stat: os.stat_result, + pyc: Path, + ) -> bool: + proc_pyc = "{}.{}".format(pyc, os.getpid()) + try: + fp = open(proc_pyc, "wb") + except OSError as e: + state.trace( + "error writing pyc file at {}: errno={}".format(proc_pyc, e.errno) + ) + return False + try: + _write_pyc_fp(fp, source_stat, co) + os.rename(proc_pyc, fspath(pyc)) + except OSError as e: + state.trace("error writing pyc file at {}: {}".format(pyc, e)) + # we ignore any failure to write the cache file + # there are many reasons, permission-denied, pycache dir being a + # file etc. + return False + finally: + fp.close() + return True -def _rewrite_test(config, fn): - """Try to read and rewrite *fn* and return the code object.""" - state = config._assertstate - try: - stat = fn.stat() - source = fn.read("rb") - except EnvironmentError: - return None, None - if ASCII_IS_DEFAULT_ENCODING: - # ASCII is the default encoding in Python 2. Without a coding - # declaration, Python 2 will complain about any bytes in the file - # outside the ASCII range. Sadly, this behavior does not extend to - # compile() or ast.parse(), which prefer to interpret the bytes as - # latin-1. (At least they properly handle explicit coding cookies.) To - # preserve this error behavior, we could force ast.parse() to use ASCII - # as the encoding by inserting a coding cookie. Unfortunately, that - # messes up line numbers. Thus, we have to check ourselves if anything - # is outside the ASCII range in the case no encoding is explicitly - # declared. For more context, see issue #269. Yay for Python 3 which - # gets this right. - end1 = source.find("\n") - end2 = source.find("\n", end1 + 1) - if ( - not source.startswith(BOM_UTF8) - and cookie_re.match(source[0:end1]) is None - and cookie_re.match(source[end1 + 1 : end2]) is None - ): - if hasattr(state, "_indecode"): - # encodings imported us again, so don't rewrite. - return None, None - state._indecode = True - try: - try: - source.decode("ascii") - except UnicodeDecodeError: - # Let it fail in real import. - return None, None - finally: - del state._indecode - try: - tree = ast.parse(source, filename=fn.strpath) - except SyntaxError: - # Let this pop up again in the real import. - state.trace("failed to parse: %r" % (fn,)) - return None, None - rewrite_asserts(tree, fn, config) - try: - co = compile(tree, fn.strpath, "exec", dont_inherit=True) - except SyntaxError: - # It's possible that this error is from some bug in the - # assertion rewriting, but I don't know of a fast way to tell. - state.trace("failed to compile: %r" % (fn,)) - return None, None + +def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: + """Read and rewrite *fn* and return the code object.""" + fn_ = fspath(fn) + stat = os.stat(fn_) + with open(fn_, "rb") as f: + source = f.read() + tree = ast.parse(source, filename=fn_) + rewrite_asserts(tree, source, fn_, config) + co = compile(tree, fn_, "exec", dont_inherit=True) return stat, co -def _read_pyc(source, pyc, trace=lambda x: None): +def _read_pyc( + source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None +) -> Optional[types.CodeType]: """Possibly read a pytest pyc containing rewritten code. Return rewritten code if successful or None if not. """ try: - fp = open(pyc, "rb") - except IOError: + fp = open(fspath(pyc), "rb") + except OSError: return None with fp: try: - mtime = int(source.mtime()) - size = source.size() + stat_result = os.stat(fspath(source)) + mtime = int(stat_result.st_mtime) + size = stat_result.st_size data = fp.read(12) - except EnvironmentError as e: - trace("_read_pyc(%s): EnvironmentError %s" % (source, e)) + except OSError as e: + trace("_read_pyc({}): OSError {}".format(source, e)) return None # Check for invalid or out of date pyc file. if ( len(data) != 12 - or data[:4] != imp.get_magic() + or data[:4] != importlib.util.MAGIC_NUMBER or struct.unpack("<LL", data[4:]) != (mtime & 0xFFFFFFFF, size & 0xFFFFFFFF) ): trace("_read_pyc(%s): invalid or out of date pyc" % source) @@ -440,7 +391,7 @@ def _read_pyc(source, pyc, trace=lambda x: None): try: co = marshal.load(fp) except Exception as e: - trace("_read_pyc(%s): marshal.load error %s" % (source, e)) + trace("_read_pyc({}): marshal.load error {}".format(source, e)) return None if not isinstance(co, types.CodeType): trace("_read_pyc(%s): not a code object" % source) @@ -448,13 +399,18 @@ def _read_pyc(source, pyc, trace=lambda x: None): return co -def rewrite_asserts(mod, module_path=None, config=None): +def rewrite_asserts( + mod: ast.Module, + source: bytes, + module_path: Optional[str] = None, + config: Optional[Config] = None, +) -> None: """Rewrite the assert statements in mod.""" - AssertionRewriter(module_path, config).run(mod) + AssertionRewriter(module_path, config, source).run(mod) -def _saferepr(obj): - """Get a safe repr of an object for assertion error messages. +def _saferepr(obj: object) -> str: + r"""Get a safe repr of an object for assertion error messages. The assertion formatting (util.format_explanation()) requires newlines to be escaped since they are a special character for it. @@ -462,38 +418,25 @@ def _saferepr(obj): custom repr it is possible to contain one of the special escape sequences, especially '\n{' and '\n}' are likely to be present in JSON reprs. - """ - r = saferepr(obj) - # only occurs in python2.x, repr must return text in python3+ - if isinstance(r, bytes): - # Represent unprintable bytes as `\x##` - r = u"".join( - u"\\x{:x}".format(ord(c)) if c not in string.printable else c.decode() - for c in r - ) - return r.replace(u"\n", u"\\n") + return saferepr(obj).replace("\n", "\\n") -def _format_assertmsg(obj): - """Format the custom assertion message given. +def _format_assertmsg(obj: object) -> str: + r"""Format the custom assertion message given. For strings this simply replaces newlines with '\n~' so that util.format_explanation() will preserve them instead of escaping newlines. For other objects saferepr() is used first. - """ # reprlib appears to have a bug which means that if a string # contains a newline it gets escaped, however if an object has a # .__repr__() which contains newlines it does not get escaped. # However in either case we want to preserve the newline. - replaces = [(u"\n", u"\n~"), (u"%", u"%%")] - if not isinstance(obj, six.string_types): + replaces = [("\n", "\n~"), ("%", "%%")] + if not isinstance(obj, str): obj = saferepr(obj) - replaces.append((u"\\n", u"\n~")) - - if isinstance(obj, bytes): - replaces = [(r1.encode(), r2.encode()) for r1, r2 in replaces] + replaces.append(("\\n", "\n~")) for r1, r2 in replaces: obj = obj.replace(r1, r2) @@ -501,7 +444,7 @@ def _format_assertmsg(obj): return obj -def _should_repr_global_name(obj): +def _should_repr_global_name(obj: object) -> bool: if callable(obj): return False @@ -511,15 +454,17 @@ def _should_repr_global_name(obj): return True -def _format_boolop(explanations, is_or): +def _format_boolop(explanations: Iterable[str], is_or: bool) -> str: explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")" - if isinstance(explanation, six.text_type): - return explanation.replace(u"%", u"%%") - else: - return explanation.replace(b"%", b"%%") + return explanation.replace("%", "%%") -def _call_reprcompare(ops, results, expls, each_obj): +def _call_reprcompare( + ops: Sequence[str], + results: Sequence[bool], + expls: Sequence[str], + each_obj: Sequence[object], +) -> str: for i, res, expl in zip(range(len(ops)), results, expls): try: done = not res @@ -534,9 +479,20 @@ def _call_reprcompare(ops, results, expls, each_obj): return expl -unary_map = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"} +def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None: + if util._assertion_pass is not None: + util._assertion_pass(lineno, orig, expl) -binop_map = { + +def _check_if_assertion_pass_impl() -> bool: + """Check if any plugins implement the pytest_assertion_pass hook + in order not to generate explanation unecessarily (might be expensive).""" + return True if util._assertion_pass else False + + +UNARY_MAP = {ast.Not: "not %s", ast.Invert: "~%s", ast.USub: "-%s", ast.UAdd: "+%s"} + +BINOP_MAP = { ast.BitOr: "|", ast.BitXor: "^", ast.BitAnd: "&", @@ -559,20 +515,8 @@ binop_map = { ast.IsNot: "is not", ast.In: "in", ast.NotIn: "not in", + ast.MatMult: "@", } -# Python 3.5+ compatibility -try: - binop_map[ast.MatMult] = "@" -except AttributeError: - pass - -# Python 3.4+ compatibility -if hasattr(ast, "NameConstant"): - _NameConstant = ast.NameConstant -else: - - def _NameConstant(c): - return ast.Name(str(c), ast.Load()) def set_location(node, lineno, col_offset): @@ -590,6 +534,60 @@ def set_location(node, lineno, col_offset): return node +def _get_assertion_exprs(src: bytes) -> Dict[int, str]: + """Return a mapping from {lineno: "assertion test expression"}.""" + ret = {} # type: Dict[int, str] + + depth = 0 + lines = [] # type: List[str] + assert_lineno = None # type: Optional[int] + seen_lines = set() # type: Set[int] + + def _write_and_reset() -> None: + nonlocal depth, lines, assert_lineno, seen_lines + assert assert_lineno is not None + ret[assert_lineno] = "".join(lines).rstrip().rstrip("\\") + depth = 0 + lines = [] + assert_lineno = None + seen_lines = set() + + tokens = tokenize.tokenize(io.BytesIO(src).readline) + for tp, source, (lineno, offset), _, line in tokens: + if tp == tokenize.NAME and source == "assert": + assert_lineno = lineno + elif assert_lineno is not None: + # keep track of depth for the assert-message `,` lookup + if tp == tokenize.OP and source in "([{": + depth += 1 + elif tp == tokenize.OP and source in ")]}": + depth -= 1 + + if not lines: + lines.append(line[offset:]) + seen_lines.add(lineno) + # a non-nested comma separates the expression from the message + elif depth == 0 and tp == tokenize.OP and source == ",": + # one line assert with message + if lineno in seen_lines and len(lines) == 1: + offset_in_trimmed = offset + len(lines[-1]) - len(line) + lines[-1] = lines[-1][:offset_in_trimmed] + # multi-line assert with message + elif lineno in seen_lines: + lines[-1] = lines[-1][:offset] + # multi line assert with escapd newline before message + else: + lines.append(line[:offset]) + _write_and_reset() + elif tp in {tokenize.NEWLINE, tokenize.ENDMARKER}: + _write_and_reset() + elif lines and lineno not in seen_lines: + lines.append(line) + seen_lines.add(lineno) + + return ret + + class AssertionRewriter(ast.NodeVisitor): """Assertion rewriting implementation. @@ -606,7 +604,8 @@ class AssertionRewriter(ast.NodeVisitor): original assert statement: it rewrites the test of an assertion to provide intermediate values and replace it with an if statement which raises an assertion error with a detailed explanation in - case the expression is false. + case the expression is false and calls pytest_assertion_pass hook + if expression is true. For this .visit_Assert() uses the visitor pattern to visit all the AST nodes of the ast.Assert.test field, each visit call returning @@ -624,9 +623,10 @@ class AssertionRewriter(ast.NodeVisitor): by statements. Variables are created using .variable() and have the form of "@py_assert0". - :on_failure: The AST statements which will be executed if the - assertion test fails. This is the code which will construct - the failure message and raises the AssertionError. + :expl_stmts: The AST statements which will be executed to get + data from the assertion. This is the code which will construct + the detailed assertion message that is used in the AssertionError + or for the pytest_assertion_pass hook. :explanation_specifiers: A dict filled by .explanation_param() with %-formatting placeholders and their corresponding @@ -639,15 +639,27 @@ class AssertionRewriter(ast.NodeVisitor): This state is reset on every new assert statement visited and used by the other visitors. - """ - def __init__(self, module_path, config): - super(AssertionRewriter, self).__init__() + def __init__( + self, module_path: Optional[str], config: Optional[Config], source: bytes + ) -> None: + super().__init__() self.module_path = module_path self.config = config + if config is not None: + self.enable_assertion_pass_hook = config.getini( + "enable_assertion_pass_hook" + ) + else: + self.enable_assertion_pass_hook = False + self.source = source - def run(self, mod): + @functools.lru_cache(maxsize=1) + def _assert_expr_to_lineno(self) -> Dict[int, str]: + return _get_assertion_exprs(self.source) + + def run(self, mod: ast.Module) -> None: """Find all assert statements in *mod* and rewrite them.""" if not mod.body: # Nothing to do. @@ -655,7 +667,7 @@ class AssertionRewriter(ast.NodeVisitor): # Insert some special imports at the top of the module but after any # docstrings and __future__ imports. aliases = [ - ast.alias(six.moves.builtins.__name__, "@py_builtins"), + ast.alias("builtins", "@py_builtins"), ast.alias("_pytest.assertion.rewrite", "@pytest_ar"), ] doc = getattr(mod, "docstring", None) @@ -675,13 +687,18 @@ class AssertionRewriter(ast.NodeVisitor): return expect_docstring = False elif ( - not isinstance(item, ast.ImportFrom) - or item.level > 0 - or item.module != "__future__" + isinstance(item, ast.ImportFrom) + and item.level == 0 + and item.module == "__future__" ): - lineno = item.lineno + pass + else: break pos += 1 + # Special case: for a decorated function, set the lineno to that of the + # first decorator, not the `def`. Issue #4984. + if isinstance(item, ast.FunctionDef) and item.decorator_list: + lineno = item.decorator_list[0].lineno else: lineno = item.lineno imports = [ @@ -689,12 +706,12 @@ class AssertionRewriter(ast.NodeVisitor): ] mod.body[pos:pos] = imports # Collect asserts. - nodes = [mod] + nodes = [mod] # type: List[ast.AST] while nodes: node = nodes.pop() for name, field in ast.iter_fields(node): if isinstance(field, list): - new = [] + new = [] # type: List[ast.AST] for i, child in enumerate(field): if isinstance(child, ast.Assert): # Transform assert. @@ -713,51 +730,50 @@ class AssertionRewriter(ast.NodeVisitor): nodes.append(field) @staticmethod - def is_rewrite_disabled(docstring): + def is_rewrite_disabled(docstring: str) -> bool: return "PYTEST_DONT_REWRITE" in docstring - def variable(self): + def variable(self) -> str: """Get a new variable.""" # Use a character invalid in python identifiers to avoid clashing. name = "@py_assert" + str(next(self.variable_counter)) self.variables.append(name) return name - def assign(self, expr): + def assign(self, expr: ast.expr) -> ast.Name: """Give *expr* a name.""" name = self.variable() self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr)) return ast.Name(name, ast.Load()) - def display(self, expr): + def display(self, expr: ast.expr) -> ast.expr: """Call saferepr on the expression.""" return self.helper("_saferepr", expr) - def helper(self, name, *args): + def helper(self, name: str, *args: ast.expr) -> ast.expr: """Call a helper in this module.""" py_name = ast.Name("@pytest_ar", ast.Load()) attr = ast.Attribute(py_name, name, ast.Load()) - return ast_Call(attr, list(args), []) + return ast.Call(attr, list(args), []) - def builtin(self, name): + def builtin(self, name: str) -> ast.Attribute: """Return the builtin called *name*.""" builtin_name = ast.Name("@py_builtins", ast.Load()) return ast.Attribute(builtin_name, name, ast.Load()) - def explanation_param(self, expr): + def explanation_param(self, expr: ast.expr) -> str: """Return a new named %-formatting placeholder for expr. This creates a %-formatting placeholder for expr in the current formatting context, e.g. ``%(py0)s``. The placeholder and expr are placed in the current format context so that it can be used on the next call to .pop_format_context(). - """ specifier = "py" + str(next(self.variable_counter)) self.explanation_specifiers[specifier] = expr return "%(" + specifier + ")s" - def push_format_context(self): + def push_format_context(self) -> None: """Create a new formatting context. The format context is used for when an explanation wants to @@ -766,19 +782,17 @@ class AssertionRewriter(ast.NodeVisitor): .explanation_param(). Finally .pop_format_context() is used to format a string of %-formatted values as added by .explanation_param(). - """ - self.explanation_specifiers = {} + self.explanation_specifiers = {} # type: Dict[str, ast.expr] self.stack.append(self.explanation_specifiers) - def pop_format_context(self, expl_expr): + def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: """Format the %-formatted string with current format context. - The expl_expr should be an ast.Str instance constructed from + The expl_expr should be an str ast.expr instance constructed from the %-placeholders created by .explanation_param(). This will - add the required code to format said string to .on_failure and + add the required code to format said string to .expl_stmts and return the ast.Name instance of the formatted string. - """ current = self.stack.pop() if self.stack: @@ -787,172 +801,193 @@ class AssertionRewriter(ast.NodeVisitor): format_dict = ast.Dict(keys, list(current.values())) form = ast.BinOp(expl_expr, ast.Mod(), format_dict) name = "@py_format" + str(next(self.variable_counter)) - self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form)) + if self.enable_assertion_pass_hook: + self.format_variables.append(name) + self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) return ast.Name(name, ast.Load()) - def generic_visit(self, node): + def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]: """Handle expressions we don't have custom code for.""" assert isinstance(node, ast.expr) res = self.assign(node) return res, self.explanation_param(self.display(res)) - def visit_Assert(self, assert_): + def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: """Return the AST statements to replace the ast.Assert instance. This rewrites the test of an assertion to provide intermediate values and replace it with an if statement which raises an assertion error with a detailed explanation in case the expression is false. - """ if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: from _pytest.warning_types import PytestAssertRewriteWarning import warnings + # TODO: This assert should not be needed. + assert self.module_path is not None warnings.warn_explicit( PytestAssertRewriteWarning( "assertion is always true, perhaps remove parentheses?" ), category=None, - filename=str(self.module_path), + filename=fspath(self.module_path), lineno=assert_.lineno, ) - self.statements = [] - self.variables = [] + self.statements = [] # type: List[ast.stmt] + self.variables = [] # type: List[str] self.variable_counter = itertools.count() - self.stack = [] - self.on_failure = [] + + if self.enable_assertion_pass_hook: + self.format_variables = [] # type: List[str] + + self.stack = [] # type: List[Dict[str, ast.expr]] + self.expl_stmts = [] # type: List[ast.stmt] self.push_format_context() # Rewrite assert into a bunch of statements. top_condition, explanation = self.visit(assert_.test) - # If in a test module, check if directly asserting None, in order to warn [Issue #3191] - if self.module_path is not None: - self.statements.append( - self.warn_about_none_ast( - top_condition, module_path=self.module_path, lineno=assert_.lineno + + negation = ast.UnaryOp(ast.Not(), top_condition) + + if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook + msg = self.pop_format_context(ast.Str(explanation)) + + # Failed + if assert_.msg: + assertmsg = self.helper("_format_assertmsg", assert_.msg) + gluestr = "\n>assert " + else: + assertmsg = ast.Str("") + gluestr = "assert " + err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg) + err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) + err_name = ast.Name("AssertionError", ast.Load()) + fmt = self.helper("_format_explanation", err_msg) + exc = ast.Call(err_name, [fmt], []) + raise_ = ast.Raise(exc, None) + statements_fail = [] + statements_fail.extend(self.expl_stmts) + statements_fail.append(raise_) + + # Passed + fmt_pass = self.helper("_format_explanation", msg) + orig = self._assert_expr_to_lineno()[assert_.lineno] + hook_call_pass = ast.Expr( + self.helper( + "_call_assertion_pass", + ast.Num(assert_.lineno), + ast.Str(orig), + fmt_pass, ) ) - # Create failure message. - body = self.on_failure - negation = ast.UnaryOp(ast.Not(), top_condition) - self.statements.append(ast.If(negation, body, [])) - if assert_.msg: - assertmsg = self.helper("_format_assertmsg", assert_.msg) - explanation = "\n>assert " + explanation - else: - assertmsg = ast.Str("") - explanation = "assert " + explanation - template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) - msg = self.pop_format_context(template) - fmt = self.helper("_format_explanation", msg) - err_name = ast.Name("AssertionError", ast.Load()) - exc = ast_Call(err_name, [fmt], []) - if sys.version_info[0] >= 3: + # If any hooks implement assert_pass hook + hook_impl_test = ast.If( + self.helper("_check_if_assertion_pass_impl"), + self.expl_stmts + [hook_call_pass], + [], + ) + statements_pass = [hook_impl_test] + + # Test for assertion condition + main_test = ast.If(negation, statements_fail, statements_pass) + self.statements.append(main_test) + if self.format_variables: + variables = [ + ast.Name(name, ast.Store()) for name in self.format_variables + ] + clear_format = ast.Assign(variables, ast.NameConstant(None)) + self.statements.append(clear_format) + + else: # Original assertion rewriting + # Create failure message. + body = self.expl_stmts + self.statements.append(ast.If(negation, body, [])) + if assert_.msg: + assertmsg = self.helper("_format_assertmsg", assert_.msg) + explanation = "\n>assert " + explanation + else: + assertmsg = ast.Str("") + explanation = "assert " + explanation + template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) + msg = self.pop_format_context(template) + fmt = self.helper("_format_explanation", msg) + err_name = ast.Name("AssertionError", ast.Load()) + exc = ast.Call(err_name, [fmt], []) raise_ = ast.Raise(exc, None) - else: - raise_ = ast.Raise(exc, None, None) - body.append(raise_) + + body.append(raise_) + # Clear temporary variables by setting them to None. if self.variables: variables = [ast.Name(name, ast.Store()) for name in self.variables] - clear = ast.Assign(variables, _NameConstant(None)) + clear = ast.Assign(variables, ast.NameConstant(None)) self.statements.append(clear) # Fix line numbers. for stmt in self.statements: set_location(stmt, assert_.lineno, assert_.col_offset) return self.statements - def warn_about_none_ast(self, node, module_path, lineno): - """ - Returns an AST issuing a warning if the value of node is `None`. - This is used to warn the user when asserting a function that asserts - internally already. - See issue #3191 for more details. - """ - - # Using parse because it is different between py2 and py3. - AST_NONE = ast.parse("None").body[0].value - val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE]) - send_warning = ast.parse( - """ -from _pytest.warning_types import PytestAssertRewriteWarning -from warnings import warn_explicit -warn_explicit( - PytestAssertRewriteWarning('asserting the value None, please use "assert is None"'), - category=None, - filename={filename!r}, - lineno={lineno}, -) - """.format( - filename=module_path.strpath, lineno=lineno - ) - ).body - return ast.If(val_is_none, send_warning, []) - - def visit_Name(self, name): + def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. - locs = ast_Call(self.builtin("locals"), [], []) + locs = ast.Call(self.builtin("locals"), [], []) inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) dorepr = self.helper("_should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) expr = ast.IfExp(test, self.display(name), ast.Str(name.id)) return name, self.explanation_param(expr) - def visit_BoolOp(self, boolop): + def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: res_var = self.variable() expl_list = self.assign(ast.List([], ast.Load())) app = ast.Attribute(expl_list, "append", ast.Load()) is_or = int(isinstance(boolop.op, ast.Or)) body = save = self.statements - fail_save = self.on_failure + fail_save = self.expl_stmts levels = len(boolop.values) - 1 self.push_format_context() - # Process each operand, short-circuting if needed. + # Process each operand, short-circuiting if needed. for i, v in enumerate(boolop.values): if i: - fail_inner = [] + fail_inner = [] # type: List[ast.stmt] # cond is set in a prior loop iteration below - self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa - self.on_failure = fail_inner + self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa + self.expl_stmts = fail_inner self.push_format_context() res, expl = self.visit(v) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) expl_format = self.pop_format_context(ast.Str(expl)) - call = ast_Call(app, [expl_format], []) - self.on_failure.append(ast.Expr(call)) + call = ast.Call(app, [expl_format], []) + self.expl_stmts.append(ast.Expr(call)) if i < levels: - cond = res + cond = res # type: ast.expr if is_or: cond = ast.UnaryOp(ast.Not(), cond) - inner = [] + inner = [] # type: List[ast.stmt] self.statements.append(ast.If(cond, inner, [])) self.statements = body = inner self.statements = save - self.on_failure = fail_save + self.expl_stmts = fail_save expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or)) expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) - def visit_UnaryOp(self, unary): - pattern = unary_map[unary.op.__class__] + def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]: + pattern = UNARY_MAP[unary.op.__class__] operand_res, operand_expl = self.visit(unary.operand) res = self.assign(ast.UnaryOp(unary.op, operand_res)) return res, pattern % (operand_expl,) - def visit_BinOp(self, binop): - symbol = binop_map[binop.op.__class__] + def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: + symbol = BINOP_MAP[binop.op.__class__] left_expr, left_expl = self.visit(binop.left) right_expr, right_expl = self.visit(binop.right) - explanation = "(%s %s %s)" % (left_expl, symbol, right_expl) + explanation = "({} {} {})".format(left_expl, symbol, right_expl) res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) return res, explanation - def visit_Call_35(self, call): - """ - visit `ast.Call` nodes on Python3.5 and after - """ + def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: new_func, func_expl = self.visit(call.func) arg_expls = [] new_args = [] @@ -969,58 +1004,20 @@ warn_explicit( else: # **args have `arg` keywords with an .arg of None arg_expls.append("**" + expl) - expl = "%s(%s)" % (func_expl, ", ".join(arg_expls)) + expl = "{}({})".format(func_expl, ", ".join(arg_expls)) new_call = ast.Call(new_func, new_args, new_kwargs) res = self.assign(new_call) res_expl = self.explanation_param(self.display(res)) - outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl) + outer_expl = "{}\n{{{} = {}\n}}".format(res_expl, res_expl, expl) return res, outer_expl - def visit_Starred(self, starred): - # From Python 3.5, a Starred node can appear in a function call + def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]: + # From Python 3.5, a Starred node can appear in a function call. res, expl = self.visit(starred.value) new_starred = ast.Starred(res, starred.ctx) return new_starred, "*" + expl - def visit_Call_legacy(self, call): - """ - visit `ast.Call nodes on 3.4 and below` - """ - new_func, func_expl = self.visit(call.func) - arg_expls = [] - new_args = [] - new_kwargs = [] - new_star = new_kwarg = None - for arg in call.args: - res, expl = self.visit(arg) - new_args.append(res) - arg_expls.append(expl) - for keyword in call.keywords: - res, expl = self.visit(keyword.value) - new_kwargs.append(ast.keyword(keyword.arg, res)) - arg_expls.append(keyword.arg + "=" + expl) - if call.starargs: - new_star, expl = self.visit(call.starargs) - arg_expls.append("*" + expl) - if call.kwargs: - new_kwarg, expl = self.visit(call.kwargs) - arg_expls.append("**" + expl) - expl = "%s(%s)" % (func_expl, ", ".join(arg_expls)) - new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg) - res = self.assign(new_call) - res_expl = self.explanation_param(self.display(res)) - outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl) - return res, outer_expl - - # ast.Call signature changed on 3.5, - # conditionally change which methods is named - # visit_Call depending on Python version - if sys.version_info >= (3, 5): - visit_Call = visit_Call_35 - else: - visit_Call = visit_Call_legacy - - def visit_Attribute(self, attr): + def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: if not isinstance(attr.ctx, ast.Load): return self.generic_visit(attr) value, value_expl = self.visit(attr.value) @@ -1030,7 +1027,7 @@ warn_explicit( expl = pat % (res_expl, res_expl, value_expl, attr.attr) return res, expl - def visit_Compare(self, comp): + def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: self.push_format_context() left_res, left_expl = self.visit(comp.left) if isinstance(comp.left, (ast.Compare, ast.BoolOp)): @@ -1047,9 +1044,9 @@ warn_explicit( if isinstance(next_operand, (ast.Compare, ast.BoolOp)): next_expl = "({})".format(next_expl) results.append(next_res) - sym = binop_map[op.__class__] + sym = BINOP_MAP[op.__class__] syms.append(ast.Str(sym)) - expl = "%s %s %s" % (left_expl, sym, next_expl) + expl = "{} {} {}".format(left_expl, sym, next_expl) expls.append(ast.Str(expl)) res_expr = ast.Compare(left_res, [op], [next_res]) self.statements.append(ast.Assign([store_names[i]], res_expr)) @@ -1063,7 +1060,43 @@ warn_explicit( ast.Tuple(results, ast.Load()), ) if len(comp.ops) > 1: - res = ast.BoolOp(ast.And(), load_names) + res = ast.BoolOp(ast.And(), load_names) # type: ast.expr else: res = load_names[0] return res, self.explanation_param(self.pop_format_context(expl_call)) + + +def try_makedirs(cache_dir: Path) -> bool: + """Attempt to create the given directory and sub-directories exist. + + Returns True if successful or if it already exists. + """ + try: + os.makedirs(fspath(cache_dir), exist_ok=True) + except (FileNotFoundError, NotADirectoryError, FileExistsError): + # One of the path components was not a directory: + # - we're in a zip file + # - it is a file + return False + except PermissionError: + return False + except OSError as e: + # as of now, EROFS doesn't have an equivalent OSError-subclass + if e.errno == errno.EROFS: + return False + raise + return True + + +def get_cache_dir(file_path: Path) -> Path: + """Return the cache directory to write .pyc files for the given .py file path.""" + if sys.version_info >= (3, 8) and sys.pycache_prefix: + # given: + # prefix = '/tmp/pycs' + # path = '/home/user/proj/test_app.py' + # we want: + # '/tmp/pycs/home/user/proj' + return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) + else: + # classic pycache directory + return file_path.parent / "__pycache__" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py index 525896ea9ad..c572cc74461 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py @@ -1,49 +1,47 @@ -# -*- coding: utf-8 -*- -""" -Utilities for truncating assertion output. +"""Utilities for truncating assertion output. Current default behaviour is to truncate assertion explanations at ~8 terminal lines, unless running in "-vv" mode or running on CI. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os +from typing import List +from typing import Optional + +from _pytest.nodes import Item -import six DEFAULT_MAX_LINES = 8 DEFAULT_MAX_CHARS = 8 * 80 USAGE_MSG = "use '-vv' to show" -def truncate_if_required(explanation, item, max_length=None): - """ - Truncate this assertion explanation if the given test item is eligible. - """ +def truncate_if_required( + explanation: List[str], item: Item, max_length: Optional[int] = None +) -> List[str]: + """Truncate this assertion explanation if the given test item is eligible.""" if _should_truncate_item(item): return _truncate_explanation(explanation) return explanation -def _should_truncate_item(item): - """ - Whether or not this test item is eligible for truncation. - """ +def _should_truncate_item(item: Item) -> bool: + """Whether or not this test item is eligible for truncation.""" verbose = item.config.option.verbose return verbose < 2 and not _running_on_ci() -def _running_on_ci(): +def _running_on_ci() -> bool: """Check if we're currently running on a CI system.""" env_vars = ["CI", "BUILD_NUMBER"] return any(var in os.environ for var in env_vars) -def _truncate_explanation(input_lines, max_lines=None, max_chars=None): - """ - Truncate given list of strings that makes up the assertion explanation. +def _truncate_explanation( + input_lines: List[str], + max_lines: Optional[int] = None, + max_chars: Optional[int] = None, +) -> List[str]: + """Truncate given list of strings that makes up the assertion explanation. Truncates to either 8 lines, or 640 characters - whichever the input reaches first. The remaining lines will be replaced by a usage message. @@ -76,11 +74,11 @@ def _truncate_explanation(input_lines, max_lines=None, max_chars=None): else: msg += " ({} lines hidden)".format(truncated_line_count) msg += ", {}".format(USAGE_MSG) - truncated_explanation.extend([six.text_type(""), six.text_type(msg)]) + truncated_explanation.extend(["", str(msg)]) return truncated_explanation -def _truncate_by_char_count(input_lines, max_chars): +def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]: # Check if truncation required if len("".join(input_lines)) <= max_chars: return input_lines diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py index c382f1c6091..e80e476c84a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/assertion/util.py @@ -1,16 +1,20 @@ -# -*- coding: utf-8 -*- -"""Utilities for assertion debugging""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Utilities for assertion debugging.""" +import collections.abc import pprint - -import six +from typing import AbstractSet +from typing import Any +from typing import Callable +from typing import Iterable +from typing import List +from typing import Mapping +from typing import Optional +from typing import Sequence +from typing import Tuple import _pytest._code -from ..compat import Sequence from _pytest import outcomes +from _pytest._io.saferepr import _pformat_dispatch +from _pytest._io.saferepr import safeformat from _pytest._io.saferepr import saferepr from _pytest.compat import ATTRS_EQ_FIELD @@ -18,20 +22,15 @@ from _pytest.compat import ATTRS_EQ_FIELD # interpretation code and assertion rewriter to detect this plugin was # loaded and in turn call the hooks defined here as part of the # DebugInterpreter. -_reprcompare = None +_reprcompare = None # type: Optional[Callable[[str, object, object], Optional[str]]] +# Works similarly as _reprcompare attribute. Is populated with the hook call +# when pytest_runtest_setup is called. +_assertion_pass = None # type: Optional[Callable[[int, str, str], None]] -# the re-encoding is needed for python2 repr -# with non-ascii characters (see issue 877 and 1379) -def ecu(s): - if isinstance(s, bytes): - return s.decode("UTF-8", "replace") - else: - return s - -def format_explanation(explanation): - """This formats an explanation +def format_explanation(explanation: str) -> str: + r"""Format an explanation. Normally all embedded newlines are escaped, however there are three exceptions: \n{, \n} and \n~. The first two are intended @@ -40,20 +39,19 @@ def format_explanation(explanation): for when one explanation needs to span multiple lines, e.g. when displaying diffs. """ - explanation = ecu(explanation) lines = _split_explanation(explanation) result = _format_lines(lines) - return u"\n".join(result) + return "\n".join(result) -def _split_explanation(explanation): - """Return a list of individual lines in the explanation +def _split_explanation(explanation: str) -> List[str]: + r"""Return a list of individual lines in the explanation. This will return a list of lines split on '\n{', '\n}' and '\n~'. Any other newlines will be escaped and appear in the line as the literal '\n' characters. """ - raw_lines = (explanation or u"").split("\n") + raw_lines = (explanation or "").split("\n") lines = [raw_lines[0]] for values in raw_lines[1:]: if values and values[0] in ["{", "}", "~", ">"]: @@ -63,28 +61,28 @@ def _split_explanation(explanation): return lines -def _format_lines(lines): - """Format the individual lines +def _format_lines(lines: Sequence[str]) -> List[str]: + """Format the individual lines. - This will replace the '{', '}' and '~' characters of our mini - formatting language with the proper 'where ...', 'and ...' and ' + - ...' text, taking care of indentation along the way. + This will replace the '{', '}' and '~' characters of our mini formatting + language with the proper 'where ...', 'and ...' and ' + ...' text, taking + care of indentation along the way. Return a list of formatted lines. """ - result = lines[:1] + result = list(lines[:1]) stack = [0] stackcnt = [0] for line in lines[1:]: if line.startswith("{"): if stackcnt[-1]: - s = u"and " + s = "and " else: - s = u"where " + s = "where " stack.append(len(result)) stackcnt[-1] += 1 stackcnt.append(0) - result.append(u" +" + u" " * (len(stack) - 1) + s + line[1:]) + result.append(" +" + " " * (len(stack) - 1) + s + line[1:]) elif line.startswith("}"): stack.pop() stackcnt.pop() @@ -93,43 +91,36 @@ def _format_lines(lines): assert line[0] in ["~", ">"] stack[-1] += 1 indent = len(stack) if line.startswith("~") else len(stack) - 1 - result.append(u" " * indent + line[1:]) + result.append(" " * indent + line[1:]) assert len(stack) == 1 return result -# Provide basestring in python3 -try: - basestring = basestring -except NameError: - basestring = str - +def issequence(x: Any) -> bool: + return isinstance(x, collections.abc.Sequence) and not isinstance(x, str) -def issequence(x): - return isinstance(x, Sequence) and not isinstance(x, basestring) +def istext(x: Any) -> bool: + return isinstance(x, str) -def istext(x): - return isinstance(x, basestring) - -def isdict(x): +def isdict(x: Any) -> bool: return isinstance(x, dict) -def isset(x): +def isset(x: Any) -> bool: return isinstance(x, (set, frozenset)) -def isdatacls(obj): +def isdatacls(obj: Any) -> bool: return getattr(obj, "__dataclass_fields__", None) is not None -def isattrs(obj): +def isattrs(obj: Any) -> bool: return getattr(obj, "__attrs_attrs__", None) is not None -def isiterable(obj): +def isiterable(obj: Any) -> bool: try: iter(obj) return not istext(obj) @@ -137,38 +128,27 @@ def isiterable(obj): return False -def assertrepr_compare(config, op, left, right): - """Return specialised explanations for some operators/operands""" - width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op - left_repr = saferepr(left, maxsize=int(width // 2)) - right_repr = saferepr(right, maxsize=width - len(left_repr)) +def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]: + """Return specialised explanations for some operators/operands.""" + verbose = config.getoption("verbose") + if verbose > 1: + left_repr = safeformat(left) + right_repr = safeformat(right) + else: + # XXX: "15 chars indentation" is wrong + # ("E AssertionError: assert "); should use term width. + maxsize = ( + 80 - 15 - len(op) - 2 + ) // 2 # 15 chars indentation, 1 space around op + left_repr = saferepr(left, maxsize=maxsize) + right_repr = saferepr(right, maxsize=maxsize) - summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr)) + summary = "{} {} {}".format(left_repr, op, right_repr) - verbose = config.getoption("verbose") explanation = None try: if op == "==": - if istext(left) and istext(right): - explanation = _diff_text(left, right, verbose) - else: - if issequence(left) and issequence(right): - explanation = _compare_eq_sequence(left, right, verbose) - elif isset(left) and isset(right): - explanation = _compare_eq_set(left, right, verbose) - elif isdict(left) and isdict(right): - explanation = _compare_eq_dict(left, right, verbose) - elif type(left) == type(right) and (isdatacls(left) or isattrs(left)): - type_fn = (isdatacls, isattrs) - explanation = _compare_eq_cls(left, right, verbose, type_fn) - elif verbose > 0: - explanation = _compare_eq_verbose(left, right) - if isiterable(left) and isiterable(right): - expl = _compare_eq_iterable(left, right, verbose) - if explanation is not None: - explanation.extend(expl) - else: - explanation = expl + explanation = _compare_eq_any(left, right, verbose) elif op == "not in": if istext(left) and istext(right): explanation = _notin_text(left, right, verbose) @@ -176,9 +156,10 @@ def assertrepr_compare(config, op, left, right): raise except Exception: explanation = [ - u"(pytest_assertion plugin: representation of details failed. " - u"Probably an object has a faulty __repr__.)", - six.text_type(_pytest._code.ExceptionInfo.from_current()), + "(pytest_assertion plugin: representation of details failed: {}.".format( + _pytest._code.ExceptionInfo.from_current()._getreprcrash() + ), + " Probably an object has a faulty __repr__.)", ] if not explanation: @@ -187,33 +168,38 @@ def assertrepr_compare(config, op, left, right): return [summary] + explanation -def _diff_text(left, right, verbose=0): - """Return the explanation for the diff between text or bytes. +def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]: + explanation = [] + if istext(left) and istext(right): + explanation = _diff_text(left, right, verbose) + else: + if issequence(left) and issequence(right): + explanation = _compare_eq_sequence(left, right, verbose) + elif isset(left) and isset(right): + explanation = _compare_eq_set(left, right, verbose) + elif isdict(left) and isdict(right): + explanation = _compare_eq_dict(left, right, verbose) + elif type(left) == type(right) and (isdatacls(left) or isattrs(left)): + type_fn = (isdatacls, isattrs) + explanation = _compare_eq_cls(left, right, verbose, type_fn) + elif verbose > 0: + explanation = _compare_eq_verbose(left, right) + if isiterable(left) and isiterable(right): + expl = _compare_eq_iterable(left, right, verbose) + explanation.extend(expl) + return explanation + + +def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: + """Return the explanation for the diff between text. Unless --verbose is used this will skip leading and trailing characters which are identical to keep the diff minimal. - - If the input are bytes they will be safely converted to text. """ from difflib import ndiff - explanation = [] + explanation = [] # type: List[str] - def escape_for_readable_diff(binary_text): - """ - Ensures that the internal string is always valid unicode, converting any bytes safely to valid unicode. - This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape - newlines and carriage returns (#429). - """ - r = six.text_type(repr(binary_text)[1:-1]) - r = r.replace(r"\n", "\n") - r = r.replace(r"\r", "\r") - return r - - if isinstance(left, bytes): - left = escape_for_readable_diff(left) - if isinstance(right, bytes): - right = escape_for_readable_diff(right) if verbose < 1: i = 0 # just in case left or right has zero length for i in range(min(len(left), len(right))): @@ -222,7 +208,7 @@ def _diff_text(left, right, verbose=0): if i > 42: i -= 10 # Provide some context explanation = [ - u"Skipping %s identical leading characters in diff, use -v to show" % i + "Skipping %s identical leading characters in diff, use -v to show" % i ] left = left[i:] right = right[i:] @@ -233,8 +219,8 @@ def _diff_text(left, right, verbose=0): if i > 42: i -= 10 # Provide some context explanation += [ - u"Skipping {} identical trailing " - u"characters in diff, use -v to show".format(i) + "Skipping {} identical trailing " + "characters in diff, use -v to show".format(i) ] left = left[:-i] right = right[:-i] @@ -242,59 +228,107 @@ def _diff_text(left, right, verbose=0): if left.isspace() or right.isspace(): left = repr(str(left)) right = repr(str(right)) - explanation += [u"Strings contain only whitespace, escaping them using repr()"] + explanation += ["Strings contain only whitespace, escaping them using repr()"] + # "right" is the expected base against which we compare "left", + # see https://github.com/pytest-dev/pytest/issues/3333 explanation += [ line.strip("\n") - for line in ndiff(left.splitlines(keepends), right.splitlines(keepends)) + for line in ndiff(right.splitlines(keepends), left.splitlines(keepends)) ] return explanation -def _compare_eq_verbose(left, right): +def _compare_eq_verbose(left: Any, right: Any) -> List[str]: keepends = True left_lines = repr(left).splitlines(keepends) right_lines = repr(right).splitlines(keepends) - explanation = [] - explanation += [u"-" + line for line in left_lines] - explanation += [u"+" + line for line in right_lines] + explanation = [] # type: List[str] + explanation += ["+" + line for line in left_lines] + explanation += ["-" + line for line in right_lines] return explanation -def _compare_eq_iterable(left, right, verbose=0): +def _surrounding_parens_on_own_lines(lines: List[str]) -> None: + """Move opening/closing parenthesis/bracket to own lines.""" + opening = lines[0][:1] + if opening in ["(", "[", "{"]: + lines[0] = " " + lines[0][1:] + lines[:] = [opening] + lines + closing = lines[-1][-1:] + if closing in [")", "]", "}"]: + lines[-1] = lines[-1][:-1] + "," + lines[:] = lines + [closing] + + +def _compare_eq_iterable( + left: Iterable[Any], right: Iterable[Any], verbose: int = 0 +) -> List[str]: if not verbose: - return [u"Use -v to get the full diff"] + return ["Use -v to get the full diff"] # dynamic import to speedup pytest import difflib - try: - left_formatting = pprint.pformat(left).splitlines() - right_formatting = pprint.pformat(right).splitlines() - explanation = [u"Full diff:"] - except Exception: - # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling - # sorted() on a list would raise. See issue #718. - # As a workaround, the full diff is generated by using the repr() string of each item of each container. - left_formatting = sorted(repr(x) for x in left) - right_formatting = sorted(repr(x) for x in right) - explanation = [u"Full diff (fallback to calling repr on each item):"] + left_formatting = pprint.pformat(left).splitlines() + right_formatting = pprint.pformat(right).splitlines() + + # Re-format for different output lengths. + lines_left = len(left_formatting) + lines_right = len(right_formatting) + if lines_left != lines_right: + left_formatting = _pformat_dispatch(left).splitlines() + right_formatting = _pformat_dispatch(right).splitlines() + + if lines_left > 1 or lines_right > 1: + _surrounding_parens_on_own_lines(left_formatting) + _surrounding_parens_on_own_lines(right_formatting) + + explanation = ["Full diff:"] + # "right" is the expected base against which we compare "left", + # see https://github.com/pytest-dev/pytest/issues/3333 explanation.extend( - line.strip() for line in difflib.ndiff(left_formatting, right_formatting) + line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting) ) return explanation -def _compare_eq_sequence(left, right, verbose=0): - explanation = [] +def _compare_eq_sequence( + left: Sequence[Any], right: Sequence[Any], verbose: int = 0 +) -> List[str]: + comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) + explanation = [] # type: List[str] len_left = len(left) len_right = len(right) for i in range(min(len_left, len_right)): if left[i] != right[i]: - explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])] + if comparing_bytes: + # when comparing bytes, we want to see their ascii representation + # instead of their numeric values (#5260) + # using a slice gives us the ascii representation: + # >>> s = b'foo' + # >>> s[0] + # 102 + # >>> s[0:1] + # b'f' + left_value = left[i : i + 1] + right_value = right[i : i + 1] + else: + left_value = left[i] + right_value = right[i] + + explanation += [ + "At index {} diff: {!r} != {!r}".format(i, left_value, right_value) + ] break - len_diff = len_left - len_right + if comparing_bytes: + # when comparing bytes, it doesn't help to show the "sides contain one or more + # items" longer explanation, so skip it + + return explanation + + len_diff = len_left - len_right if len_diff: if len_diff > 0: dir_with_more = "Left" @@ -305,51 +339,57 @@ def _compare_eq_sequence(left, right, verbose=0): extra = saferepr(right[len_left]) if len_diff == 1: - explanation += [u"%s contains one more item: %s" % (dir_with_more, extra)] + explanation += [ + "{} contains one more item: {}".format(dir_with_more, extra) + ] else: explanation += [ - u"%s contains %d more items, first extra item: %s" + "%s contains %d more items, first extra item: %s" % (dir_with_more, len_diff, extra) ] return explanation -def _compare_eq_set(left, right, verbose=0): +def _compare_eq_set( + left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0 +) -> List[str]: explanation = [] diff_left = left - right diff_right = right - left if diff_left: - explanation.append(u"Extra items in the left set:") + explanation.append("Extra items in the left set:") for item in diff_left: explanation.append(saferepr(item)) if diff_right: - explanation.append(u"Extra items in the right set:") + explanation.append("Extra items in the right set:") for item in diff_right: explanation.append(saferepr(item)) return explanation -def _compare_eq_dict(left, right, verbose=0): - explanation = [] +def _compare_eq_dict( + left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0 +) -> List[str]: + explanation = [] # type: List[str] set_left = set(left) set_right = set(right) common = set_left.intersection(set_right) same = {k: left[k] for k in common if left[k] == right[k]} if same and verbose < 2: - explanation += [u"Omitting %s identical items, use -vv to show" % len(same)] + explanation += ["Omitting %s identical items, use -vv to show" % len(same)] elif same: - explanation += [u"Common items:"] + explanation += ["Common items:"] explanation += pprint.pformat(same).splitlines() diff = {k for k in common if left[k] != right[k]} if diff: - explanation += [u"Differing items:"] + explanation += ["Differing items:"] for k in diff: explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] extra_left = set_left - set_right len_extra_left = len(extra_left) if len_extra_left: explanation.append( - u"Left contains %d more item%s:" + "Left contains %d more item%s:" % (len_extra_left, "" if len_extra_left == 1 else "s") ) explanation.extend( @@ -359,7 +399,7 @@ def _compare_eq_dict(left, right, verbose=0): len_extra_right = len(extra_right) if len_extra_right: explanation.append( - u"Right contains %d more item%s:" + "Right contains %d more item%s:" % (len_extra_right, "" if len_extra_right == 1 else "s") ) explanation.extend( @@ -368,7 +408,12 @@ def _compare_eq_dict(left, right, verbose=0): return explanation -def _compare_eq_cls(left, right, verbose, type_fns): +def _compare_eq_cls( + left: Any, + right: Any, + verbose: int, + type_fns: Tuple[Callable[[Any], bool], Callable[[Any], bool]], +) -> List[str]: isdatacls, isattrs = type_fns if isdatacls(left): all_fields = left.__dataclass_fields__ @@ -379,6 +424,7 @@ def _compare_eq_cls(left, right, verbose, type_fns): field.name for field in all_fields if getattr(field, ATTRS_EQ_FIELD) ] + indent = " " same = [] diff = [] for field in fields_to_check: @@ -388,34 +434,45 @@ def _compare_eq_cls(left, right, verbose, type_fns): diff.append(field) explanation = [] + if same or diff: + explanation += [""] if same and verbose < 2: - explanation.append(u"Omitting %s identical items, use -vv to show" % len(same)) + explanation.append("Omitting %s identical items, use -vv to show" % len(same)) elif same: - explanation += [u"Matching attributes:"] + explanation += ["Matching attributes:"] explanation += pprint.pformat(same).splitlines() if diff: - explanation += [u"Differing attributes:"] + explanation += ["Differing attributes:"] + explanation += pprint.pformat(diff).splitlines() for field in diff: + field_left = getattr(left, field) + field_right = getattr(right, field) + explanation += [ + "", + "Drill down into differing attribute %s:" % field, + ("%s%s: %r != %r") % (indent, field, field_left, field_right), + ] explanation += [ - (u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field)) + indent + line + for line in _compare_eq_any(field_left, field_right, verbose) ] return explanation -def _notin_text(term, text, verbose=0): +def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: index = text.find(term) head = text[:index] tail = text[index + len(term) :] correct_text = head + tail - diff = _diff_text(correct_text, text, verbose) - newdiff = [u"%s is contained here:" % saferepr(term, maxsize=42)] + diff = _diff_text(text, correct_text, verbose) + newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)] for line in diff: - if line.startswith(u"Skipping"): + if line.startswith("Skipping"): continue - if line.startswith(u"- "): + if line.startswith("- "): continue - if line.startswith(u"+ "): - newdiff.append(u" " + line[2:]) + if line.startswith("+ "): + newdiff.append(" " + line[2:]) else: newdiff.append(line) return newdiff diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py index f5c55454849..b04305ed9d2 100755 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/cacheprovider.py @@ -1,29 +1,38 @@ -# -*- coding: utf-8 -*- -""" -merged implementation of the cache provider - -the name cache was not chosen to ensure pluggy automatically -ignores the external pytest-cache -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Implementation of the cache provider.""" +# This plugin was not named "cache" to avoid conflicts with the external +# pytest-cache version. import json import os -from collections import OrderedDict +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Set +from typing import Union import attr import py -import six import pytest -from .compat import _PY2 as PY2 from .pathlib import Path from .pathlib import resolve_from_str from .pathlib import rm_rf - -README_CONTENT = u"""\ +from .reports import CollectReport +from _pytest import nodes +from _pytest._io import TerminalWriter +from _pytest.compat import final +from _pytest.compat import order_preserving_dict +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureRequest +from _pytest.main import Session +from _pytest.python import Module +from _pytest.reports import TestReport + + +README_CONTENT = """\ # pytest cache directory # This directory contains data from the pytest's cache plugin, @@ -31,7 +40,7 @@ which provides the `--lf` and `--ff` options, as well as the `cache` fixture. **Do not** commit this to version control. -See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information. +See [the docs](https://docs.pytest.org/en/stable/cache.html) for more information. """ CACHEDIR_TAG_CONTENT = b"""\ @@ -42,79 +51,97 @@ Signature: 8a477f597d28d172789f06886806bc55 """ +@final @attr.s -class Cache(object): - _cachedir = attr.ib(repr=False) - _config = attr.ib(repr=False) +class Cache: + _cachedir = attr.ib(type=Path, repr=False) + _config = attr.ib(type=Config, repr=False) + + # sub-directory under cache-dir for directories created by "makedir" + _CACHE_PREFIX_DIRS = "d" + + # sub-directory under cache-dir for values created by "set" + _CACHE_PREFIX_VALUES = "v" @classmethod - def for_config(cls, config): + def for_config(cls, config: Config) -> "Cache": cachedir = cls.cache_dir_from_config(config) - if config.getoption("cacheclear") and cachedir.exists(): - rm_rf(cachedir) - cachedir.mkdir() + if config.getoption("cacheclear") and cachedir.is_dir(): + cls.clear_cache(cachedir) return cls(cachedir, config) + @classmethod + def clear_cache(cls, cachedir: Path) -> None: + """Clear the sub-directories used to hold cached directories and values.""" + for prefix in (cls._CACHE_PREFIX_DIRS, cls._CACHE_PREFIX_VALUES): + d = cachedir / prefix + if d.is_dir(): + rm_rf(d) + @staticmethod - def cache_dir_from_config(config): - return resolve_from_str(config.getini("cache_dir"), config.rootdir) + def cache_dir_from_config(config: Config) -> Path: + return resolve_from_str(config.getini("cache_dir"), config.rootpath) - def warn(self, fmt, **args): - from _pytest.warnings import _issue_warning_captured + def warn(self, fmt: str, **args: object) -> None: + import warnings from _pytest.warning_types import PytestCacheWarning - _issue_warning_captured( + warnings.warn( PytestCacheWarning(fmt.format(**args) if args else fmt), self._config.hook, stacklevel=3, ) - def makedir(self, name): - """ return a directory path object with the given name. If the - directory does not yet exist, it will be created. You can use it - to manage files likes e. g. store/retrieve database - dumps across test sessions. + def makedir(self, name: str) -> py.path.local: + """Return a directory path object with the given name. + + If the directory does not yet exist, it will be created. You can use + it to manage files to e.g. store/retrieve database dumps across test + sessions. - :param name: must be a string not containing a ``/`` separator. - Make sure the name contains your plugin or application - identifiers to prevent clashes with other cache users. + :param name: + Must be a string not containing a ``/`` separator. + Make sure the name contains your plugin or application + identifiers to prevent clashes with other cache users. """ - name = Path(name) - if len(name.parts) > 1: + path = Path(name) + if len(path.parts) > 1: raise ValueError("name is not allowed to contain path separators") - res = self._cachedir.joinpath("d", name) + res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path) res.mkdir(exist_ok=True, parents=True) return py.path.local(res) - def _getvaluepath(self, key): - return self._cachedir.joinpath("v", Path(key)) + def _getvaluepath(self, key: str) -> Path: + return self._cachedir.joinpath(self._CACHE_PREFIX_VALUES, Path(key)) - def get(self, key, default): - """ return cached value for the given key. If no value - was yet cached or the value cannot be read, the specified - default is returned. + def get(self, key: str, default): + """Return the cached value for the given key. - :param key: must be a ``/`` separated value. Usually the first - name is the name of your plugin or your application. - :param default: must be provided in case of a cache-miss or - invalid cache values. + If no value was yet cached or the value cannot be read, the specified + default is returned. + :param key: + Must be a ``/`` separated value. Usually the first + name is the name of your plugin or your application. + :param default: + The value to return in case of a cache-miss or invalid cache value. """ path = self._getvaluepath(key) try: with path.open("r") as f: return json.load(f) - except (ValueError, IOError, OSError): + except (ValueError, OSError): return default - def set(self, key, value): - """ save value for the given key. + def set(self, key: str, value: object) -> None: + """Save value for the given key. - :param key: must be a ``/`` separated value. Usually the first - name is the name of your plugin or your application. - :param value: must be of any combination of basic - python types, including nested types - like e. g. lists of dictionaries. + :param key: + Must be a ``/`` separated value. Usually the first + name is the name of your plugin or your application. + :param value: + Must be of any combination of basic python types, + including nested types like lists of dictionaries. """ path = self._getvaluepath(key) try: @@ -123,80 +150,138 @@ class Cache(object): else: cache_dir_exists_already = self._cachedir.exists() path.parent.mkdir(exist_ok=True, parents=True) - except (IOError, OSError): + except OSError: self.warn("could not create cache path {path}", path=path) return if not cache_dir_exists_already: self._ensure_supporting_files() + data = json.dumps(value, indent=2, sort_keys=True) try: - f = path.open("wb" if PY2 else "w") - except (IOError, OSError): + f = path.open("w") + except OSError: self.warn("cache could not write path {path}", path=path) else: with f: - json.dump(value, f, indent=2, sort_keys=True) + f.write(data) - def _ensure_supporting_files(self): + def _ensure_supporting_files(self) -> None: """Create supporting files in the cache dir that are not really part of the cache.""" readme_path = self._cachedir / "README.md" readme_path.write_text(README_CONTENT) gitignore_path = self._cachedir.joinpath(".gitignore") - msg = u"# Created by pytest automatically.\n*" + msg = "# Created by pytest automatically.\n*\n" gitignore_path.write_text(msg, encoding="UTF-8") cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG") cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT) -class LFPlugin(object): - """ Plugin which implements the --lf (run last-failing) option """ +class LFPluginCollWrapper: + def __init__(self, lfplugin: "LFPlugin") -> None: + self.lfplugin = lfplugin + self._collected_at_least_one_failure = False + + @pytest.hookimpl(hookwrapper=True) + def pytest_make_collect_report(self, collector: nodes.Collector): + if isinstance(collector, Session): + out = yield + res = out.get_result() # type: CollectReport + + # Sort any lf-paths to the beginning. + lf_paths = self.lfplugin._last_failed_paths + res.result = sorted( + res.result, key=lambda x: 0 if Path(str(x.fspath)) in lf_paths else 1, + ) + return + + elif isinstance(collector, Module): + if Path(str(collector.fspath)) in self.lfplugin._last_failed_paths: + out = yield + res = out.get_result() + result = res.result + lastfailed = self.lfplugin.lastfailed + + # Only filter with known failures. + if not self._collected_at_least_one_failure: + if not any(x.nodeid in lastfailed for x in result): + return + self.lfplugin.config.pluginmanager.register( + LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip" + ) + self._collected_at_least_one_failure = True + + session = collector.session + result[:] = [ + x + for x in result + if x.nodeid in lastfailed + # Include any passed arguments (not trivial to filter). + or session.isinitpath(x.fspath) + # Keep all sub-collectors. + or isinstance(x, nodes.Collector) + ] + return + yield + + +class LFPluginCollSkipfiles: + def __init__(self, lfplugin: "LFPlugin") -> None: + self.lfplugin = lfplugin + + @pytest.hookimpl + def pytest_make_collect_report( + self, collector: nodes.Collector + ) -> Optional[CollectReport]: + if isinstance(collector, Module): + if Path(str(collector.fspath)) not in self.lfplugin._last_failed_paths: + self.lfplugin._skipped_files += 1 + + return CollectReport( + collector.nodeid, "passed", longrepr=None, result=[] + ) + return None + + +class LFPlugin: + """Plugin which implements the --lf (run last-failing) option.""" - def __init__(self, config): + def __init__(self, config: Config) -> None: self.config = config active_keys = "lf", "failedfirst" self.active = any(config.getoption(key) for key in active_keys) - self.lastfailed = config.cache.get("cache/lastfailed", {}) - self._previously_failed_count = None - self._report_status = None + assert config.cache + self.lastfailed = config.cache.get( + "cache/lastfailed", {} + ) # type: Dict[str, bool] + self._previously_failed_count = None # type: Optional[int] + self._report_status = None # type: Optional[str] self._skipped_files = 0 # count skipped files during collection due to --lf - def last_failed_paths(self): - """Returns a set with all Paths()s of the previously failed nodeids (cached). - """ - try: - return self._last_failed_paths - except AttributeError: - rootpath = Path(self.config.rootdir) - result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed} - result = {x for x in result if x.exists()} - self._last_failed_paths = result - return result - - def pytest_ignore_collect(self, path): - """ - Ignore this file path if we are in --lf mode and it is not in the list of - previously failed files. - """ - if self.active and self.config.getoption("lf") and path.isfile(): - last_failed_paths = self.last_failed_paths() - if last_failed_paths: - skip_it = Path(path) not in self.last_failed_paths() - if skip_it: - self._skipped_files += 1 - return skip_it - - def pytest_report_collectionfinish(self): + if config.getoption("lf"): + self._last_failed_paths = self.get_last_failed_paths() + config.pluginmanager.register( + LFPluginCollWrapper(self), "lfplugin-collwrapper" + ) + + def get_last_failed_paths(self) -> Set[Path]: + """Return a set with all Paths()s of the previously failed nodeids.""" + rootpath = self.config.rootpath + result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed} + return {x for x in result if x.exists()} + + def pytest_report_collectionfinish(self) -> Optional[str]: if self.active and self.config.getoption("verbose") >= 0: return "run-last-failure: %s" % self._report_status + return None - def pytest_runtest_logreport(self, report): + def pytest_runtest_logreport(self, report: TestReport) -> None: if (report.when == "call" and report.passed) or report.skipped: self.lastfailed.pop(report.nodeid, None) elif report.failed: self.lastfailed[report.nodeid] = True - def pytest_collectreport(self, report): + def pytest_collectreport(self, report: CollectReport) -> None: passed = report.outcome in ("passed", "skipped") if passed: if report.nodeid in self.lastfailed: @@ -205,7 +290,12 @@ class LFPlugin(object): else: self.lastfailed[report.nodeid] = True - def pytest_collection_modifyitems(self, session, config, items): + @pytest.hookimpl(hookwrapper=True, tryfirst=True) + def pytest_collection_modifyitems( + self, config: Config, items: List[nodes.Item] + ) -> Generator[None, None, None]: + yield + if not self.active: return @@ -247,33 +337,40 @@ class LFPlugin(object): self._report_status = "no previously failed tests, " if self.config.getoption("last_failed_no_failures") == "none": self._report_status += "deselecting all items." - config.hook.pytest_deselected(items=items) + config.hook.pytest_deselected(items=items[:]) items[:] = [] else: self._report_status += "not deselecting items." - def pytest_sessionfinish(self, session): + def pytest_sessionfinish(self, session: Session) -> None: config = self.config - if config.getoption("cacheshow") or hasattr(config, "slaveinput"): + if config.getoption("cacheshow") or hasattr(config, "workerinput"): return + assert config.cache is not None saved_lastfailed = config.cache.get("cache/lastfailed", {}) if saved_lastfailed != self.lastfailed: config.cache.set("cache/lastfailed", self.lastfailed) -class NFPlugin(object): - """ Plugin which implements the --nf (run new-first) option """ +class NFPlugin: + """Plugin which implements the --nf (run new-first) option.""" - def __init__(self, config): + def __init__(self, config: Config) -> None: self.config = config self.active = config.option.newfirst - self.cached_nodeids = config.cache.get("cache/nodeids", []) + assert config.cache is not None + self.cached_nodeids = set(config.cache.get("cache/nodeids", [])) + + @pytest.hookimpl(hookwrapper=True, tryfirst=True) + def pytest_collection_modifyitems( + self, items: List[nodes.Item] + ) -> Generator[None, None, None]: + yield - def pytest_collection_modifyitems(self, session, config, items): if self.active: - new_items = OrderedDict() - other_items = OrderedDict() + new_items = order_preserving_dict() # type: Dict[str, nodes.Item] + other_items = order_preserving_dict() # type: Dict[str, nodes.Item] for item in items: if item.nodeid not in self.cached_nodeids: new_items[item.nodeid] = item @@ -281,22 +378,28 @@ class NFPlugin(object): other_items[item.nodeid] = item items[:] = self._get_increasing_order( - six.itervalues(new_items) - ) + self._get_increasing_order(six.itervalues(other_items)) - self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)] + new_items.values() + ) + self._get_increasing_order(other_items.values()) + self.cached_nodeids.update(new_items) + else: + self.cached_nodeids.update(item.nodeid for item in items) - def _get_increasing_order(self, items): + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]: return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True) - def pytest_sessionfinish(self, session): + def pytest_sessionfinish(self) -> None: config = self.config - if config.getoption("cacheshow") or hasattr(config, "slaveinput"): + if config.getoption("cacheshow") or hasattr(config, "workerinput"): return - config.cache.set("cache/nodeids", self.cached_nodeids) + if config.getoption("collectonly"): + return + + assert config.cache is not None + config.cache.set("cache/nodeids", sorted(self.cached_nodeids)) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--lf", @@ -311,9 +414,9 @@ def pytest_addoption(parser): "--failed-first", action="store_true", dest="failedfirst", - help="run all tests but run the last failures first. " + help="run all tests, but run the last failures first.\n" "This may re-order tests and thus lead to " - "repeated fixture setup/teardown", + "repeated fixture setup/teardown.", ) group.addoption( "--nf", @@ -354,54 +457,59 @@ def pytest_addoption(parser): ) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.cacheshow: from _pytest.main import wrap_session return wrap_session(config, cacheshow) + return None @pytest.hookimpl(tryfirst=True) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.cache = Cache.for_config(config) config.pluginmanager.register(LFPlugin(config), "lfplugin") config.pluginmanager.register(NFPlugin(config), "nfplugin") @pytest.fixture -def cache(request): - """ - Return a cache object that can persist state between testing sessions. +def cache(request: FixtureRequest) -> Cache: + """Return a cache object that can persist state between testing sessions. cache.get(key, default) cache.set(key, value) - Keys must be a ``/`` separated value, where the first part is usually the + Keys must be ``/`` separated strings, where the first part is usually the name of your plugin or application to avoid clashes with other cache users. Values can be any object handled by the json stdlib module. """ + assert request.config.cache is not None return request.config.cache -def pytest_report_header(config): +def pytest_report_header(config: Config) -> Optional[str]: """Display cachedir with --cache-show and if non-default.""" if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": + assert config.cache is not None cachedir = config.cache._cachedir # TODO: evaluate generating upward relative paths # starting with .., ../.. if sensible try: - displaypath = cachedir.relative_to(config.rootdir) + displaypath = cachedir.relative_to(config.rootpath) except ValueError: displaypath = cachedir return "cachedir: {}".format(displaypath) + return None -def cacheshow(config, session): +def cacheshow(config: Config, session: Session) -> int: from pprint import pformat - tw = py.io.TerminalWriter() + assert config.cache is not None + + tw = TerminalWriter() tw.line("cachedir: " + str(config.cache._cachedir)) if not config.cache._cachedir.is_dir(): tw.line("cache is empty") @@ -413,10 +521,10 @@ def cacheshow(config, session): dummy = object() basedir = config.cache._cachedir - vdir = basedir / "v" + vdir = basedir / Cache._CACHE_PREFIX_VALUES tw.sep("-", "cache values for %r" % glob) for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): - key = valpath.relative_to(vdir) + key = str(valpath.relative_to(vdir)) val = config.cache.get(key, dummy) if val is dummy: tw.line("%s contains unreadable content, will be ignored" % key) @@ -425,7 +533,7 @@ def cacheshow(config, session): for line in pformat(val).splitlines(): tw.line(" " + line) - ddir = basedir / "d" + ddir = basedir / Cache._CACHE_PREFIX_DIRS if ddir.is_dir(): contents = sorted(ddir.rglob(glob)) tw.sep("-", "cache directories for %r" % glob) @@ -433,6 +541,6 @@ def cacheshow(config, session): # if p.check(dir=1): # print("%s/" % p.relto(basedir)) if p.is_file(): - key = p.relative_to(basedir) + key = str(p.relative_to(basedir)) tw.line("{} is a file of length {:d}".format(key, p.stat().st_size)) return 0 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py index 68c17772f39..2d2b392aba8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/capture.py @@ -1,38 +1,45 @@ -# -*- coding: utf-8 -*- -""" -per-test stdout/stderr capturing mechanism. - -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import collections +"""Per-test stdout/stderr capturing mechanism.""" import contextlib +import functools import io import os import sys from io import UnsupportedOperation from tempfile import TemporaryFile - -import six +from typing import Any +from typing import AnyStr +from typing import Generator +from typing import Generic +from typing import Iterator +from typing import Optional +from typing import TextIO +from typing import Tuple +from typing import Union import pytest -from _pytest.compat import _PY3 -from _pytest.compat import CaptureIO +from _pytest.compat import final +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.fixtures import SubRequest +from _pytest.nodes import Collector +from _pytest.nodes import Item -patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} +if TYPE_CHECKING: + from typing_extensions import Literal + + _CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group._addoption( "--capture", action="store", - default="fd" if hasattr(os, "dup") else "sys", + default="fd", metavar="method", - choices=["fd", "sys", "no"], - help="per-test capturing method: one of fd|sys|no.", + choices=["fd", "sys", "no", "tee-sys"], + help="per-test capturing method: one of fd|sys|no|tee-sys.", ) group._addoption( "-s", @@ -43,8 +50,107 @@ def pytest_addoption(parser): ) +def _colorama_workaround() -> None: + """Ensure colorama is imported so that it attaches to the correct stdio + handles on Windows. + + colorama uses the terminal on import time. So if something does the + first import of colorama while I/O capture is active, colorama will + fail in various ways. + """ + if sys.platform.startswith("win32"): + try: + import colorama # noqa: F401 + except ImportError: + pass + + +def _readline_workaround() -> None: + """Ensure readline is imported so that it attaches to the correct stdio + handles on Windows. + + Pdb uses readline support where available--when not running from the Python + prompt, the readline module is not imported until running the pdb REPL. If + running pytest with the --pdb option this means the readline module is not + imported until after I/O capture has been started. + + This is a problem for pyreadline, which is often used to implement readline + support on Windows, as it does not attach to the correct handles for stdout + and/or stdin if they have been redirected by the FDCapture mechanism. This + workaround ensures that readline is imported before I/O capture is setup so + that it can attach to the actual stdin/out for the console. + + See https://github.com/pytest-dev/pytest/pull/1281. + """ + if sys.platform.startswith("win32"): + try: + import readline # noqa: F401 + except ImportError: + pass + + +def _py36_windowsconsoleio_workaround(stream: TextIO) -> None: + """Workaround for Windows Unicode console handling on Python>=3.6. + + Python 3.6 implemented Unicode console handling for Windows. This works + by reading/writing to the raw console handle using + ``{Read,Write}ConsoleW``. + + The problem is that we are going to ``dup2`` over the stdio file + descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the + handles used by Python to write to the console. Though there is still some + weirdness and the console handle seems to only be closed randomly and not + on the first call to ``CloseHandle``, or maybe it gets reopened with the + same handle value when we suspend capturing. + + The workaround in this case will reopen stdio with a different fd which + also means a different handle by replicating the logic in + "Py_lifecycle.c:initstdio/create_stdio". + + :param stream: + In practice ``sys.stdout`` or ``sys.stderr``, but given + here as parameter for unittesting purposes. + + See https://github.com/pytest-dev/py/issues/103. + """ + if ( + not sys.platform.startswith("win32") + or sys.version_info[:2] < (3, 6) + or hasattr(sys, "pypy_version_info") + ): + return + + # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666). + if not hasattr(stream, "buffer"): # type: ignore[unreachable] + return + + buffered = hasattr(stream.buffer, "raw") + raw_stdout = stream.buffer.raw if buffered else stream.buffer # type: ignore[attr-defined] + + if not isinstance(raw_stdout, io._WindowsConsoleIO): # type: ignore[attr-defined] + return + + def _reopen_stdio(f, mode): + if not buffered and mode[0] == "w": + buffering = 0 + else: + buffering = -1 + + return io.TextIOWrapper( + open(os.dup(f.fileno()), mode, buffering), # type: ignore[arg-type] + f.encoding, + f.errors, + f.newlines, + f.line_buffering, + ) + + sys.stdin = _reopen_stdio(sys.stdin, "rb") + sys.stdout = _reopen_stdio(sys.stdout, "wb") + sys.stderr = _reopen_stdio(sys.stderr, "wb") + + @pytest.hookimpl(hookwrapper=True) -def pytest_load_initial_conftests(early_config, parser, args): +def pytest_load_initial_conftests(early_config: Config): ns = early_config.known_args_namespace if ns.capture == "fd": _py36_windowsconsoleio_workaround(sys.stdout) @@ -54,10 +160,10 @@ def pytest_load_initial_conftests(early_config, parser, args): capman = CaptureManager(ns.capture) pluginmanager.register(capman, "capturemanager") - # make sure that capturemanager is properly reset at final shutdown + # Make sure that capturemanager is properly reset at final shutdown. early_config.add_cleanup(capman.stop_global_capturing) - # finally trigger conftest loading but while capturing (issue93) + # Finally trigger conftest loading but while capturing (issue #93). capman.start_global_capturing() outcome = yield capman.suspend_global_capture() @@ -67,421 +173,399 @@ def pytest_load_initial_conftests(early_config, parser, args): sys.stderr.write(err) -class CaptureManager(object): - """ - Capture plugin, manages that the appropriate capture method is enabled/disabled during collection and each - test phase (setup, call, teardown). After each of those points, the captured output is obtained and - attached to the collection/runtest report. +# IO Helpers. - There are two levels of capture: - * global: which is enabled by default and can be suppressed by the ``-s`` option. This is always enabled/disabled - during collection and each test phase. - * fixture: when a test function or one of its fixture depend on the ``capsys`` or ``capfd`` fixtures. In this - case special handling is needed to ensure the fixtures take precedence over the global capture. - """ - def __init__(self, method): - self._method = method - self._global_capturing = None - self._current_item = None - - def __repr__(self): - return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % ( - self._method, - self._global_capturing, - self._current_item, - ) +class EncodedFile(io.TextIOWrapper): + __slots__ = () - def _getcapture(self, method): - if method == "fd": - return MultiCapture(out=True, err=True, Capture=FDCapture) - elif method == "sys": - return MultiCapture(out=True, err=True, Capture=SysCapture) - elif method == "no": - return MultiCapture(out=False, err=False, in_=False) - raise ValueError("unknown capturing method: %r" % method) # pragma: no cover + @property + def name(self) -> str: + # Ensure that file.name is a string. Workaround for a Python bug + # fixed in >=3.7.4: https://bugs.python.org/issue36015 + return repr(self.buffer) - def is_capturing(self): - if self.is_globally_capturing(): - return "global" - capture_fixture = getattr(self._current_item, "_capture_fixture", None) - if capture_fixture is not None: - return ( - "fixture %s" % self._current_item._capture_fixture.request.fixturename - ) - return False + @property + def mode(self) -> str: + # TextIOWrapper doesn't expose a mode, but at least some of our + # tests check it. + return self.buffer.mode.replace("b", "") - # Global capturing control - def is_globally_capturing(self): - return self._method != "no" +class CaptureIO(io.TextIOWrapper): + def __init__(self) -> None: + super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True) - def start_global_capturing(self): - assert self._global_capturing is None - self._global_capturing = self._getcapture(self._method) - self._global_capturing.start_capturing() + def getvalue(self) -> str: + assert isinstance(self.buffer, io.BytesIO) + return self.buffer.getvalue().decode("UTF-8") - def stop_global_capturing(self): - if self._global_capturing is not None: - self._global_capturing.pop_outerr_to_orig() - self._global_capturing.stop_capturing() - self._global_capturing = None - def resume_global_capture(self): - # During teardown of the python process, and on rare occasions, capture - # attributes can be `None` while trying to resume global capture. - if self._global_capturing is not None: - self._global_capturing.resume_capturing() +class TeeCaptureIO(CaptureIO): + def __init__(self, other: TextIO) -> None: + self._other = other + super().__init__() - def suspend_global_capture(self, in_=False): - cap = getattr(self, "_global_capturing", None) - if cap is not None: - cap.suspend_capturing(in_=in_) + def write(self, s: str) -> int: + super().write(s) + return self._other.write(s) - def suspend(self, in_=False): - # Need to undo local capsys-et-al if it exists before disabling global capture. - self.suspend_fixture(self._current_item) - self.suspend_global_capture(in_) - def resume(self): - self.resume_global_capture() - self.resume_fixture(self._current_item) +class DontReadFromInput: + encoding = None - def read_global_capture(self): - return self._global_capturing.readouterr() + def read(self, *args): + raise OSError( + "pytest: reading from stdin while output is captured! Consider using `-s`." + ) - # Fixture Control (it's just forwarding, think about removing this later) + readline = read + readlines = read + __next__ = read - def activate_fixture(self, item): - """If the current item is using ``capsys`` or ``capfd``, activate them so they take precedence over - the global capture. - """ - fixture = getattr(item, "_capture_fixture", None) - if fixture is not None: - fixture._start() - - def deactivate_fixture(self, item): - """Deactivates the ``capsys`` or ``capfd`` fixture of this item, if any.""" - fixture = getattr(item, "_capture_fixture", None) - if fixture is not None: - fixture.close() - - def suspend_fixture(self, item): - fixture = getattr(item, "_capture_fixture", None) - if fixture is not None: - fixture._suspend() - - def resume_fixture(self, item): - fixture = getattr(item, "_capture_fixture", None) - if fixture is not None: - fixture._resume() + def __iter__(self): + return self - # Helper context managers + def fileno(self) -> int: + raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()") - @contextlib.contextmanager - def global_and_fixture_disabled(self): - """Context manager to temporarily disable global and current fixture capturing.""" - self.suspend() - try: - yield - finally: - self.resume() + def isatty(self) -> bool: + return False - @contextlib.contextmanager - def item_capture(self, when, item): - self.resume_global_capture() - self.activate_fixture(item) - try: - yield - finally: - self.deactivate_fixture(item) - self.suspend_global_capture(in_=False) + def close(self) -> None: + pass - out, err = self.read_global_capture() - item.add_report_section(when, "stdout", out) - item.add_report_section(when, "stderr", err) + @property + def buffer(self): + return self - # Hooks - @pytest.hookimpl(hookwrapper=True) - def pytest_make_collect_report(self, collector): - if isinstance(collector, pytest.File): - self.resume_global_capture() - outcome = yield - self.suspend_global_capture() - out, err = self.read_global_capture() - rep = outcome.get_result() - if out: - rep.sections.append(("Captured stdout", out)) - if err: - rep.sections.append(("Captured stderr", err)) - else: - yield +# Capture classes. - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_protocol(self, item): - self._current_item = item - yield - self._current_item = None - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_setup(self, item): - with self.item_capture("setup", item): - yield +patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_call(self, item): - with self.item_capture("call", item): - yield - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_teardown(self, item): - with self.item_capture("teardown", item): - yield +class NoCapture: + EMPTY_BUFFER = None + __init__ = start = done = suspend = resume = lambda *args: None - @pytest.hookimpl(tryfirst=True) - def pytest_keyboard_interrupt(self, excinfo): - self.stop_global_capturing() - @pytest.hookimpl(tryfirst=True) - def pytest_internalerror(self, excinfo): - self.stop_global_capturing() +class SysCaptureBinary: + EMPTY_BUFFER = b"" -capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"} + def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None: + name = patchsysdict[fd] + self._old = getattr(sys, name) + self.name = name + if tmpfile is None: + if name == "stdin": + tmpfile = DontReadFromInput() + else: + tmpfile = CaptureIO() if not tee else TeeCaptureIO(self._old) + self.tmpfile = tmpfile + self._state = "initialized" + def repr(self, class_name: str) -> str: + return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( + class_name, + self.name, + hasattr(self, "_old") and repr(self._old) or "<UNSET>", + self._state, + self.tmpfile, + ) -def _ensure_only_one_capture_fixture(request, name): - fixtures = set(request.fixturenames) & capture_fixtures - {name} - if fixtures: - fixtures = sorted(fixtures) - fixtures = fixtures[0] if len(fixtures) == 1 else fixtures - raise request.raiseerror( - "cannot use {} and {} at the same time".format(fixtures, name) + def __repr__(self) -> str: + return "<{} {} _old={} _state={!r} tmpfile={!r}>".format( + self.__class__.__name__, + self.name, + hasattr(self, "_old") and repr(self._old) or "<UNSET>", + self._state, + self.tmpfile, ) + def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + assert ( + self._state in states + ), "cannot {} in state {!r}: expected one of {}".format( + op, self._state, ", ".join(states) + ) -@pytest.fixture -def capsys(request): - """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. + def start(self) -> None: + self._assert_state("start", ("initialized",)) + setattr(sys, self.name, self.tmpfile) + self._state = "started" - The captured output is made available via ``capsys.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - """ - _ensure_only_one_capture_fixture(request, "capsys") - with _install_capture_fixture_on_item(request, SysCapture) as fixture: - yield fixture + def snap(self): + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + def done(self) -> None: + self._assert_state("done", ("initialized", "started", "suspended", "done")) + if self._state == "done": + return + setattr(sys, self.name, self._old) + del self._old + self.tmpfile.close() + self._state = "done" -@pytest.fixture -def capsysbinary(request): - """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. + def suspend(self) -> None: + self._assert_state("suspend", ("started", "suspended")) + setattr(sys, self.name, self._old) + self._state = "suspended" - The captured output is made available via ``capsysbinary.readouterr()`` - method calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``bytes`` objects. - """ - _ensure_only_one_capture_fixture(request, "capsysbinary") - # Currently, the implementation uses the python3 specific `.buffer` - # property of CaptureIO. - if sys.version_info < (3,): - raise request.raiseerror("capsysbinary is only supported on Python 3") - with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture: - yield fixture + def resume(self) -> None: + self._assert_state("resume", ("started", "suspended")) + if self._state == "started": + return + setattr(sys, self.name, self.tmpfile) + self._state = "started" + def writeorg(self, data) -> None: + self._assert_state("writeorg", ("started", "suspended")) + self._old.flush() + self._old.buffer.write(data) + self._old.buffer.flush() -@pytest.fixture -def capfd(request): - """Enable text capturing of writes to file descriptors ``1`` and ``2``. - The captured output is made available via ``capfd.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - """ - _ensure_only_one_capture_fixture(request, "capfd") - if not hasattr(os, "dup"): - pytest.skip( - "capfd fixture needs os.dup function which is not available in this system" - ) - with _install_capture_fixture_on_item(request, FDCapture) as fixture: - yield fixture +class SysCapture(SysCaptureBinary): + EMPTY_BUFFER = "" # type: ignore[assignment] + def snap(self): + res = self.tmpfile.getvalue() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res -@pytest.fixture -def capfdbinary(request): - """Enable bytes capturing of writes to file descriptors ``1`` and ``2``. + def writeorg(self, data): + self._assert_state("writeorg", ("started", "suspended")) + self._old.write(data) + self._old.flush() - The captured output is made available via ``capfd.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``byte`` objects. - """ - _ensure_only_one_capture_fixture(request, "capfdbinary") - if not hasattr(os, "dup"): - pytest.skip( - "capfdbinary fixture needs os.dup function which is not available in this system" - ) - with _install_capture_fixture_on_item(request, FDCaptureBinary) as fixture: - yield fixture +class FDCaptureBinary: + """Capture IO to/from a given OS-level file descriptor. -@contextlib.contextmanager -def _install_capture_fixture_on_item(request, capture_class): + snap() produces `bytes`. """ - Context manager which creates a ``CaptureFixture`` instance and "installs" it on - the item/node of the given request. Used by ``capsys`` and ``capfd``. - The CaptureFixture is added as attribute of the item because it needs to accessed - by ``CaptureManager`` during its ``pytest_runtest_*`` hooks. - """ - request.node._capture_fixture = fixture = CaptureFixture(capture_class, request) - capmanager = request.config.pluginmanager.getplugin("capturemanager") - # Need to active this fixture right away in case it is being used by another fixture (setup phase). - # If this fixture is being used only by a test function (call phase), then we wouldn't need this - # activation, but it doesn't hurt. - capmanager.activate_fixture(request.node) - yield fixture - fixture.close() - del request.node._capture_fixture - - -class CaptureFixture(object): - """ - Object returned by :py:func:`capsys`, :py:func:`capsysbinary`, :py:func:`capfd` and :py:func:`capfdbinary` - fixtures. - """ + EMPTY_BUFFER = b"" - def __init__(self, captureclass, request): - self.captureclass = captureclass - self.request = request - self._capture = None - self._captured_out = self.captureclass.EMPTY_BUFFER - self._captured_err = self.captureclass.EMPTY_BUFFER + def __init__(self, targetfd: int) -> None: + self.targetfd = targetfd - def _start(self): - if self._capture is None: - self._capture = MultiCapture( - out=True, err=True, in_=False, Capture=self.captureclass + try: + os.fstat(targetfd) + except OSError: + # FD capturing is conceptually simple -- create a temporary file, + # redirect the FD to it, redirect back when done. But when the + # target FD is invalid it throws a wrench into this loveley scheme. + # + # Tests themselves shouldn't care if the FD is valid, FD capturing + # should work regardless of external circumstances. So falling back + # to just sys capturing is not a good option. + # + # Further complications are the need to support suspend() and the + # possibility of FD reuse (e.g. the tmpfile getting the very same + # target FD). The following approach is robust, I believe. + self.targetfd_invalid = os.open( + os.devnull, os.O_RDWR + ) # type: Optional[int] + os.dup2(self.targetfd_invalid, targetfd) + else: + self.targetfd_invalid = None + self.targetfd_save = os.dup(targetfd) + + if targetfd == 0: + self.tmpfile = open(os.devnull) + self.syscapture = SysCapture(targetfd) + else: + self.tmpfile = EncodedFile( + # TODO: Remove type ignore, fixed in next mypy release. + TemporaryFile(buffering=0), # type: ignore[arg-type] + encoding="utf-8", + errors="replace", + newline="", + write_through=True, ) - self._capture.start_capturing() + if targetfd in patchsysdict: + self.syscapture = SysCapture(targetfd, self.tmpfile) + else: + self.syscapture = NoCapture() - def close(self): - if self._capture is not None: - out, err = self._capture.pop_outerr_to_orig() - self._captured_out += out - self._captured_err += err - self._capture.stop_capturing() - self._capture = None + self._state = "initialized" - def readouterr(self): - """Read and return the captured output so far, resetting the internal buffer. + def __repr__(self) -> str: + return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format( + self.__class__.__name__, + self.targetfd, + self.targetfd_save, + self._state, + self.tmpfile, + ) - :return: captured content as a namedtuple with ``out`` and ``err`` string attributes - """ - captured_out, captured_err = self._captured_out, self._captured_err - if self._capture is not None: - out, err = self._capture.readouterr() - captured_out += out - captured_err += err - self._captured_out = self.captureclass.EMPTY_BUFFER - self._captured_err = self.captureclass.EMPTY_BUFFER - return CaptureResult(captured_out, captured_err) + def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + assert ( + self._state in states + ), "cannot {} in state {!r}: expected one of {}".format( + op, self._state, ", ".join(states) + ) - def _suspend(self): - """Suspends this fixture's own capturing temporarily.""" - if self._capture is not None: - self._capture.suspend_capturing() + def start(self) -> None: + """Start capturing on targetfd using memorized tmpfile.""" + self._assert_state("start", ("initialized",)) + os.dup2(self.tmpfile.fileno(), self.targetfd) + self.syscapture.start() + self._state = "started" - def _resume(self): - """Resumes this fixture's own capturing temporarily.""" - if self._capture is not None: - self._capture.resume_capturing() + def snap(self): + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def done(self) -> None: + """Stop capturing, restore streams, return original capture file, + seeked to position zero.""" + self._assert_state("done", ("initialized", "started", "suspended", "done")) + if self._state == "done": + return + os.dup2(self.targetfd_save, self.targetfd) + os.close(self.targetfd_save) + if self.targetfd_invalid is not None: + if self.targetfd_invalid != self.targetfd: + os.close(self.targetfd) + os.close(self.targetfd_invalid) + self.syscapture.done() + self.tmpfile.close() + self._state = "done" + + def suspend(self) -> None: + self._assert_state("suspend", ("started", "suspended")) + if self._state == "suspended": + return + self.syscapture.suspend() + os.dup2(self.targetfd_save, self.targetfd) + self._state = "suspended" + + def resume(self) -> None: + self._assert_state("resume", ("started", "suspended")) + if self._state == "started": + return + self.syscapture.resume() + os.dup2(self.tmpfile.fileno(), self.targetfd) + self._state = "started" + + def writeorg(self, data): + """Write to original file descriptor.""" + self._assert_state("writeorg", ("started", "suspended")) + os.write(self.targetfd_save, data) - @contextlib.contextmanager - def disabled(self): - """Temporarily disables capture while inside the 'with' block.""" - capmanager = self.request.config.pluginmanager.getplugin("capturemanager") - with capmanager.global_and_fixture_disabled(): - yield +class FDCapture(FDCaptureBinary): + """Capture IO to/from a given OS-level file descriptor. -def safe_text_dupfile(f, mode, default_encoding="UTF8"): - """ return an open text file object that's a duplicate of f on the - FD-level if possible. + snap() produces text. """ - encoding = getattr(f, "encoding", None) - try: - fd = f.fileno() - except Exception: - if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"): - # we seem to have a text stream, let's just use it - return f - else: - newfd = os.dup(fd) - if "b" not in mode: - mode += "b" - f = os.fdopen(newfd, mode, 0) # no buffering - return EncodedFile(f, encoding or default_encoding) - - -class EncodedFile(object): - errors = "strict" # possibly needed by py3 code (issue555) - - def __init__(self, buffer, encoding): - self.buffer = buffer - self.encoding = encoding - - def write(self, obj): - if isinstance(obj, six.text_type): - obj = obj.encode(self.encoding, "replace") - elif _PY3: - raise TypeError( - "write() argument must be str, not {}".format(type(obj).__name__) - ) - self.buffer.write(obj) - def writelines(self, linelist): - data = "".join(linelist) - self.write(data) + # Ignore type because it doesn't match the type in the superclass (bytes). + EMPTY_BUFFER = "" # type: ignore - @property - def name(self): - """Ensure that file.name is a string.""" - return repr(self.buffer) + def snap(self): + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res - @property - def mode(self): - return self.buffer.mode.replace("b", "") + def writeorg(self, data): + """Write to original file descriptor.""" + super().writeorg(data.encode("utf-8")) # XXX use encoding of original stream + + +# MultiCapture - def __getattr__(self, name): - return getattr(object.__getattribute__(self, "buffer"), name) +# This class was a namedtuple, but due to mypy limitation[0] it could not be +# made generic, so was replaced by a regular class which tries to emulate the +# pertinent parts of a namedtuple. If the mypy limitation is ever lifted, can +# make it a namedtuple again. +# [0]: https://github.com/python/mypy/issues/685 +@final +@functools.total_ordering +class CaptureResult(Generic[AnyStr]): + """The result of :method:`CaptureFixture.readouterr`.""" -CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"]) + # Can't use slots in Python<3.5.3 due to https://bugs.python.org/issue31272 + if sys.version_info >= (3, 5, 3): + __slots__ = ("out", "err") + def __init__(self, out: AnyStr, err: AnyStr) -> None: + self.out = out # type: AnyStr + self.err = err # type: AnyStr -class MultiCapture(object): - out = err = in_ = None + def __len__(self) -> int: + return 2 + + def __iter__(self) -> Iterator[AnyStr]: + return iter((self.out, self.err)) + + def __getitem__(self, item: int) -> AnyStr: + return tuple(self)[item] + + def _replace( + self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None + ) -> "CaptureResult[AnyStr]": + return CaptureResult( + out=self.out if out is None else out, err=self.err if err is None else err + ) + + def count(self, value: AnyStr) -> int: + return tuple(self).count(value) + + def index(self, value) -> int: + return tuple(self).index(value) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, (CaptureResult, tuple)): + return NotImplemented + return tuple(self) == tuple(other) + + def __hash__(self) -> int: + return hash(tuple(self)) + + def __lt__(self, other: object) -> bool: + if not isinstance(other, (CaptureResult, tuple)): + return NotImplemented + return tuple(self) < tuple(other) + + def __repr__(self) -> str: + return "CaptureResult(out={!r}, err={!r})".format(self.out, self.err) + + +class MultiCapture(Generic[AnyStr]): _state = None + _in_suspended = False - def __init__(self, out=True, err=True, in_=True, Capture=None): - if in_: - self.in_ = Capture(0) - if out: - self.out = Capture(1) - if err: - self.err = Capture(2) + def __init__(self, in_, out, err) -> None: + self.in_ = in_ + self.out = out + self.err = err - def __repr__(self): - return "<MultiCapture out=%r err=%r in_=%r _state=%r _in_suspended=%r>" % ( - self.out, - self.err, - self.in_, - self._state, - getattr(self, "_in_suspended", "<UNSET>"), + def __repr__(self) -> str: + return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format( + self.out, self.err, self.in_, self._state, self._in_suspended, ) - def start_capturing(self): + def start_capturing(self) -> None: self._state = "started" if self.in_: self.in_.start() @@ -490,8 +574,8 @@ class MultiCapture(object): if self.err: self.err.start() - def pop_outerr_to_orig(self): - """ pop current snapshot out/err capture and flush to orig streams. """ + def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: + """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: self.out.writeorg(out) @@ -499,7 +583,7 @@ class MultiCapture(object): self.err.writeorg(err) return out, err - def suspend_capturing(self, in_=False): + def suspend_capturing(self, in_: bool = False) -> None: self._state = "suspended" if self.out: self.out.suspend() @@ -509,18 +593,18 @@ class MultiCapture(object): self.in_.suspend() self._in_suspended = True - def resume_capturing(self): - self._state = "resumed" + def resume_capturing(self) -> None: + self._state = "started" if self.out: self.out.resume() if self.err: self.err.resume() - if hasattr(self, "_in_suspended"): + if self._in_suspended: self.in_.resume() - del self._in_suspended + self._in_suspended = False - def stop_capturing(self): - """ stop capturing and reset capturing streams """ + def stop_capturing(self) -> None: + """Stop capturing and reset capturing streams.""" if self._state == "stopped": raise ValueError("was already stopped") self._state = "stopped" @@ -531,320 +615,356 @@ class MultiCapture(object): if self.in_: self.in_.done() - def readouterr(self): - """ return snapshot unicode value of stdout/stderr capturings. """ - return CaptureResult( - self.out.snap() if self.out is not None else "", - self.err.snap() if self.err is not None else "", + def is_started(self) -> bool: + """Whether actively capturing -- not suspended or stopped.""" + return self._state == "started" + + def readouterr(self) -> CaptureResult[AnyStr]: + if self.out: + out = self.out.snap() + else: + out = "" + if self.err: + err = self.err.snap() + else: + err = "" + return CaptureResult(out, err) + + +def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: + if method == "fd": + return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2)) + elif method == "sys": + return MultiCapture(in_=SysCapture(0), out=SysCapture(1), err=SysCapture(2)) + elif method == "no": + return MultiCapture(in_=None, out=None, err=None) + elif method == "tee-sys": + return MultiCapture( + in_=None, out=SysCapture(1, tee=True), err=SysCapture(2, tee=True) ) + raise ValueError("unknown capturing method: {!r}".format(method)) -class NoCapture(object): - EMPTY_BUFFER = None - __init__ = start = done = suspend = resume = lambda *args: None +# CaptureManager and CaptureFixture + + +class CaptureManager: + """The capture plugin. + Manages that the appropriate capture method is enabled/disabled during + collection and each test phase (setup, call, teardown). After each of + those points, the captured output is obtained and attached to the + collection/runtest report. + + There are two levels of capture: -class FDCaptureBinary(object): - """Capture IO to/from a given os-level filedescriptor. + * global: enabled by default and can be suppressed by the ``-s`` + option. This is always enabled/disabled during collection and each test + phase. - snap() produces `bytes` + * fixture: when a test function or one of its fixture depend on the + ``capsys`` or ``capfd`` fixtures. In this case special handling is + needed to ensure the fixtures take precedence over the global capture. """ - EMPTY_BUFFER = b"" - _state = None + def __init__(self, method: "_CaptureMethod") -> None: + self._method = method + self._global_capturing = None # type: Optional[MultiCapture[str]] + self._capture_fixture = None # type: Optional[CaptureFixture[Any]] - def __init__(self, targetfd, tmpfile=None): - self.targetfd = targetfd - try: - self.targetfd_save = os.dup(self.targetfd) - except OSError: - self.start = lambda: None - self.done = lambda: None - else: - if targetfd == 0: - assert not tmpfile, "cannot set tmpfile with stdin" - tmpfile = open(os.devnull, "r") - self.syscapture = SysCapture(targetfd) - else: - if tmpfile is None: - f = TemporaryFile() - with f: - tmpfile = safe_text_dupfile(f, mode="wb+") - if targetfd in patchsysdict: - self.syscapture = SysCapture(targetfd, tmpfile) - else: - self.syscapture = NoCapture() - self.tmpfile = tmpfile - self.tmpfile_fd = tmpfile.fileno() - - def __repr__(self): - return "<FDCapture %s oldfd=%s _state=%r>" % ( - self.targetfd, - getattr(self, "targetfd_save", None), - self._state, + def __repr__(self) -> str: + return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format( + self._method, self._global_capturing, self._capture_fixture ) - def start(self): - """ Start capturing on targetfd using memorized tmpfile. """ - try: - os.fstat(self.targetfd_save) - except (AttributeError, OSError): - raise ValueError("saved filedescriptor not valid anymore") - os.dup2(self.tmpfile_fd, self.targetfd) - self.syscapture.start() - self._state = "started" - - def snap(self): - self.tmpfile.seek(0) - res = self.tmpfile.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res + def is_capturing(self) -> Union[str, bool]: + if self.is_globally_capturing(): + return "global" + if self._capture_fixture: + return "fixture %s" % self._capture_fixture.request.fixturename + return False - def done(self): - """ stop capturing, restore streams, return original capture file, - seeked to position zero. """ - targetfd_save = self.__dict__.pop("targetfd_save") - os.dup2(targetfd_save, self.targetfd) - os.close(targetfd_save) - self.syscapture.done() - _attempt_to_close_capture_file(self.tmpfile) - self._state = "done" + # Global capturing control - def suspend(self): - self.syscapture.suspend() - os.dup2(self.targetfd_save, self.targetfd) - self._state = "suspended" + def is_globally_capturing(self) -> bool: + return self._method != "no" - def resume(self): - self.syscapture.resume() - os.dup2(self.tmpfile_fd, self.targetfd) - self._state = "resumed" + def start_global_capturing(self) -> None: + assert self._global_capturing is None + self._global_capturing = _get_multicapture(self._method) + self._global_capturing.start_capturing() - def writeorg(self, data): - """ write to original file descriptor. """ - if isinstance(data, six.text_type): - data = data.encode("utf8") # XXX use encoding of original stream - os.write(self.targetfd_save, data) + def stop_global_capturing(self) -> None: + if self._global_capturing is not None: + self._global_capturing.pop_outerr_to_orig() + self._global_capturing.stop_capturing() + self._global_capturing = None + def resume_global_capture(self) -> None: + # During teardown of the python process, and on rare occasions, capture + # attributes can be `None` while trying to resume global capture. + if self._global_capturing is not None: + self._global_capturing.resume_capturing() -class FDCapture(FDCaptureBinary): - """Capture IO to/from a given os-level filedescriptor. + def suspend_global_capture(self, in_: bool = False) -> None: + if self._global_capturing is not None: + self._global_capturing.suspend_capturing(in_=in_) - snap() produces text - """ + def suspend(self, in_: bool = False) -> None: + # Need to undo local capsys-et-al if it exists before disabling global capture. + self.suspend_fixture() + self.suspend_global_capture(in_) - EMPTY_BUFFER = str() + def resume(self) -> None: + self.resume_global_capture() + self.resume_fixture() - def snap(self): - res = super(FDCapture, self).snap() - enc = getattr(self.tmpfile, "encoding", None) - if enc and isinstance(res, bytes): - res = six.text_type(res, enc, "replace") - return res + def read_global_capture(self) -> CaptureResult[str]: + assert self._global_capturing is not None + return self._global_capturing.readouterr() + # Fixture Control -class SysCapture(object): + def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: + if self._capture_fixture: + current_fixture = self._capture_fixture.request.fixturename + requested_fixture = capture_fixture.request.fixturename + capture_fixture.request.raiseerror( + "cannot use {} and {} at the same time".format( + requested_fixture, current_fixture + ) + ) + self._capture_fixture = capture_fixture - EMPTY_BUFFER = str() - _state = None + def unset_fixture(self) -> None: + self._capture_fixture = None - def __init__(self, fd, tmpfile=None): - name = patchsysdict[fd] - self._old = getattr(sys, name) - self.name = name - if tmpfile is None: - if name == "stdin": - tmpfile = DontReadFromInput() - else: - tmpfile = CaptureIO() - self.tmpfile = tmpfile + def activate_fixture(self) -> None: + """If the current item is using ``capsys`` or ``capfd``, activate + them so they take precedence over the global capture.""" + if self._capture_fixture: + self._capture_fixture._start() - def __repr__(self): - return "<SysCapture %s _old=%r, tmpfile=%r _state=%r>" % ( - self.name, - self._old, - self.tmpfile, - self._state, - ) + def deactivate_fixture(self) -> None: + """Deactivate the ``capsys`` or ``capfd`` fixture of this item, if any.""" + if self._capture_fixture: + self._capture_fixture.close() - def start(self): - setattr(sys, self.name, self.tmpfile) - self._state = "started" + def suspend_fixture(self) -> None: + if self._capture_fixture: + self._capture_fixture._suspend() - def snap(self): - res = self.tmpfile.getvalue() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res + def resume_fixture(self) -> None: + if self._capture_fixture: + self._capture_fixture._resume() - def done(self): - setattr(sys, self.name, self._old) - del self._old - _attempt_to_close_capture_file(self.tmpfile) - self._state = "done" + # Helper context managers - def suspend(self): - setattr(sys, self.name, self._old) - self._state = "suspended" + @contextlib.contextmanager + def global_and_fixture_disabled(self) -> Generator[None, None, None]: + """Context manager to temporarily disable global and current fixture capturing.""" + do_fixture = self._capture_fixture and self._capture_fixture._is_started() + if do_fixture: + self.suspend_fixture() + do_global = self._global_capturing and self._global_capturing.is_started() + if do_global: + self.suspend_global_capture() + try: + yield + finally: + if do_global: + self.resume_global_capture() + if do_fixture: + self.resume_fixture() - def resume(self): - setattr(sys, self.name, self.tmpfile) - self._state = "resumed" + @contextlib.contextmanager + def item_capture(self, when: str, item: Item) -> Generator[None, None, None]: + self.resume_global_capture() + self.activate_fixture() + try: + yield + finally: + self.deactivate_fixture() + self.suspend_global_capture(in_=False) - def writeorg(self, data): - self._old.write(data) - self._old.flush() + out, err = self.read_global_capture() + item.add_report_section(when, "stdout", out) + item.add_report_section(when, "stderr", err) + # Hooks -class SysCaptureBinary(SysCapture): - EMPTY_BUFFER = b"" + @pytest.hookimpl(hookwrapper=True) + def pytest_make_collect_report(self, collector: Collector): + if isinstance(collector, pytest.File): + self.resume_global_capture() + outcome = yield + self.suspend_global_capture() + out, err = self.read_global_capture() + rep = outcome.get_result() + if out: + rep.sections.append(("Captured stdout", out)) + if err: + rep.sections.append(("Captured stderr", err)) + else: + yield - def snap(self): - res = self.tmpfile.buffer.getvalue() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]: + with self.item_capture("setup", item): + yield + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]: + with self.item_capture("call", item): + yield -class DontReadFromInput(six.Iterator): - """Temporary stub class. Ideally when stdin is accessed, the - capturing should be turned off, with possibly all data captured - so far sent to the screen. This should be configurable, though, - because in automated test runs it is better to crash than - hang indefinitely. - """ + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]: + with self.item_capture("teardown", item): + yield - encoding = None + @pytest.hookimpl(tryfirst=True) + def pytest_keyboard_interrupt(self) -> None: + self.stop_global_capturing() - def read(self, *args): - raise IOError("reading from stdin while output is captured") + @pytest.hookimpl(tryfirst=True) + def pytest_internalerror(self) -> None: + self.stop_global_capturing() - readline = read - readlines = read - __next__ = read - def __iter__(self): - return self +class CaptureFixture(Generic[AnyStr]): + """Object returned by the :py:func:`capsys`, :py:func:`capsysbinary`, + :py:func:`capfd` and :py:func:`capfdbinary` fixtures.""" - def fileno(self): - raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()") + def __init__(self, captureclass, request: SubRequest) -> None: + self.captureclass = captureclass + self.request = request + self._capture = None # type: Optional[MultiCapture[AnyStr]] + self._captured_out = self.captureclass.EMPTY_BUFFER + self._captured_err = self.captureclass.EMPTY_BUFFER - def isatty(self): - return False + def _start(self) -> None: + if self._capture is None: + self._capture = MultiCapture( + in_=None, out=self.captureclass(1), err=self.captureclass(2), + ) + self._capture.start_capturing() - def close(self): - pass + def close(self) -> None: + if self._capture is not None: + out, err = self._capture.pop_outerr_to_orig() + self._captured_out += out + self._captured_err += err + self._capture.stop_capturing() + self._capture = None - @property - def buffer(self): - if sys.version_info >= (3, 0): - return self - else: - raise AttributeError("redirected stdin has no attribute buffer") + def readouterr(self) -> CaptureResult[AnyStr]: + """Read and return the captured output so far, resetting the internal + buffer. + :returns: + The captured content as a namedtuple with ``out`` and ``err`` + string attributes. + """ + captured_out, captured_err = self._captured_out, self._captured_err + if self._capture is not None: + out, err = self._capture.readouterr() + captured_out += out + captured_err += err + self._captured_out = self.captureclass.EMPTY_BUFFER + self._captured_err = self.captureclass.EMPTY_BUFFER + return CaptureResult(captured_out, captured_err) -def _colorama_workaround(): - """ - Ensure colorama is imported so that it attaches to the correct stdio - handles on Windows. + def _suspend(self) -> None: + """Suspend this fixture's own capturing temporarily.""" + if self._capture is not None: + self._capture.suspend_capturing() - colorama uses the terminal on import time. So if something does the - first import of colorama while I/O capture is active, colorama will - fail in various ways. - """ - if sys.platform.startswith("win32"): - try: - import colorama # noqa: F401 - except ImportError: - pass + def _resume(self) -> None: + """Resume this fixture's own capturing temporarily.""" + if self._capture is not None: + self._capture.resume_capturing() + def _is_started(self) -> bool: + """Whether actively capturing -- not disabled or closed.""" + if self._capture is not None: + return self._capture.is_started() + return False -def _readline_workaround(): - """ - Ensure readline is imported so that it attaches to the correct stdio - handles on Windows. + @contextlib.contextmanager + def disabled(self) -> Generator[None, None, None]: + """Temporarily disable capturing while inside the ``with`` block.""" + capmanager = self.request.config.pluginmanager.getplugin("capturemanager") + with capmanager.global_and_fixture_disabled(): + yield - Pdb uses readline support where available--when not running from the Python - prompt, the readline module is not imported until running the pdb REPL. If - running pytest with the --pdb option this means the readline module is not - imported until after I/O capture has been started. - This is a problem for pyreadline, which is often used to implement readline - support on Windows, as it does not attach to the correct handles for stdout - and/or stdin if they have been redirected by the FDCapture mechanism. This - workaround ensures that readline is imported before I/O capture is setup so - that it can attach to the actual stdin/out for the console. +# The fixtures. - See https://github.com/pytest-dev/pytest/pull/1281 - """ - if sys.platform.startswith("win32"): - try: - import readline # noqa: F401 - except ImportError: - pass +@pytest.fixture +def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: + """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. -def _py36_windowsconsoleio_workaround(stream): + The captured output is made available via ``capsys.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``text`` objects. """ - Python 3.6 implemented unicode console handling for Windows. This works - by reading/writing to the raw console handle using - ``{Read,Write}ConsoleW``. + capman = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture[str](SysCapture, request) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() - The problem is that we are going to ``dup2`` over the stdio file - descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the - handles used by Python to write to the console. Though there is still some - weirdness and the console handle seems to only be closed randomly and not - on the first call to ``CloseHandle``, or maybe it gets reopened with the - same handle value when we suspend capturing. - The workaround in this case will reopen stdio with a different fd which - also means a different handle by replicating the logic in - "Py_lifecycle.c:initstdio/create_stdio". - - :param stream: in practice ``sys.stdout`` or ``sys.stderr``, but given - here as parameter for unittesting purposes. +@pytest.fixture +def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: + """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. - See https://github.com/pytest-dev/py/issues/103 + The captured output is made available via ``capsysbinary.readouterr()`` + method calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``bytes`` objects. """ - if not sys.platform.startswith("win32") or sys.version_info[:2] < (3, 6): - return + capman = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() - # bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666) - if not hasattr(stream, "buffer"): - return - - buffered = hasattr(stream.buffer, "raw") - raw_stdout = stream.buffer.raw if buffered else stream.buffer - - if not isinstance(raw_stdout, io._WindowsConsoleIO): - return - def _reopen_stdio(f, mode): - if not buffered and mode[0] == "w": - buffering = 0 - else: - buffering = -1 +@pytest.fixture +def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: + """Enable text capturing of writes to file descriptors ``1`` and ``2``. - return io.TextIOWrapper( - open(os.dup(f.fileno()), mode, buffering), - f.encoding, - f.errors, - f.newlines, - f.line_buffering, - ) + The captured output is made available via ``capfd.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``text`` objects. + """ + capman = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture[str](FDCapture, request) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() - sys.stdin = _reopen_stdio(sys.stdin, "rb") - sys.stdout = _reopen_stdio(sys.stdout, "wb") - sys.stderr = _reopen_stdio(sys.stderr, "wb") +@pytest.fixture +def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]: + """Enable bytes capturing of writes to file descriptors ``1`` and ``2``. -def _attempt_to_close_capture_file(f): - """Suppress IOError when closing the temporary file used for capturing streams in py27 (#2370)""" - if six.PY2: - try: - f.close() - except IOError: - pass - else: - f.close() + The captured output is made available via ``capfd.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``byte`` objects. + """ + capman = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request) + capman.set_fixture(capture_fixture) + capture_fixture._start() + yield capture_fixture + capture_fixture.close() + capman.unset_fixture() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py index d0add530268..7eab2ea0c85 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/compat.py @@ -1,143 +1,168 @@ -# -*- coding: utf-8 -*- -""" -python version compatibility code -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import codecs +"""Python version compatibility code.""" +import enum import functools import inspect +import os import re import sys from contextlib import contextmanager +from inspect import Parameter +from inspect import signature +from typing import Any +from typing import Callable +from typing import Generic +from typing import Optional +from typing import overload as overload +from typing import Tuple +from typing import TypeVar +from typing import Union import attr -import py -import six -from six import text_type -import _pytest -from _pytest._io.saferepr import saferepr from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME -try: - import enum -except ImportError: # pragma: no cover - # Only available in Python 3.4+ or as a backport - enum = None - -_PY3 = sys.version_info > (3, 0) -_PY2 = not _PY3 - - -if _PY3: - from inspect import signature, Parameter as Parameter +if sys.version_info < (3, 5, 2): + TYPE_CHECKING = False # type: bool else: - from funcsigs import signature, Parameter as Parameter + from typing import TYPE_CHECKING -NOTSET = object() -PY35 = sys.version_info[:2] >= (3, 5) -PY36 = sys.version_info[:2] >= (3, 6) -MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError" +if TYPE_CHECKING: + from typing import NoReturn + from typing import Type + from typing_extensions import Final -if _PY3: - from collections.abc import MutableMapping as MappingMixin - from collections.abc import Iterable, Mapping, Sequence, Sized -else: - # those raise DeprecationWarnings in Python >=3.7 - from collections import MutableMapping as MappingMixin # noqa - from collections import Iterable, Mapping, Sequence, Sized # noqa +_T = TypeVar("_T") +_S = TypeVar("_S") -if sys.version_info >= (3, 4): - from importlib.util import spec_from_file_location -else: +# fmt: off +# Singleton type for NOTSET, as described in: +# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions +class NotSetType(enum.Enum): + token = 0 +NOTSET = NotSetType.token # type: Final # noqa: E305 +# fmt: on - def spec_from_file_location(*_, **__): - return None +MODULE_NOT_FOUND_ERROR = ( + "ModuleNotFoundError" if sys.version_info[:2] >= (3, 6) else "ImportError" +) if sys.version_info >= (3, 8): - from importlib import metadata as importlib_metadata # noqa + from importlib import metadata as importlib_metadata else: - import importlib_metadata # noqa + import importlib_metadata # noqa: F401 -def _format_args(func): +def _format_args(func: Callable[..., Any]) -> str: return str(signature(func)) -isfunction = inspect.isfunction -isclass = inspect.isclass -# used to work around a python2 exception info leak -exc_clear = getattr(sys, "exc_clear", lambda: None) # The type of re.compile objects is not exposed in Python. REGEX_TYPE = type(re.compile("")) -def is_generator(func): +if sys.version_info < (3, 6): + + def fspath(p): + """os.fspath replacement, useful to point out when we should replace it by the + real function once we drop py35.""" + return str(p) + + +else: + fspath = os.fspath + + +def is_generator(func: object) -> bool: genfunc = inspect.isgeneratorfunction(func) return genfunc and not iscoroutinefunction(func) -def iscoroutinefunction(func): - """Return True if func is a decorated coroutine function. +def iscoroutinefunction(func: object) -> bool: + """Return True if func is a coroutine function (a function defined with async + def syntax, and doesn't contain yield), or a function decorated with + @asyncio.coroutine. - Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly, - which in turns also initializes the "logging" module as side-effect (see issue #8). + Note: copied and modified from Python 3.5's builtin couroutines.py to avoid + importing asyncio directly, which in turns also initializes the "logging" + module as a side-effect (see issue #8). """ - return getattr(func, "_is_coroutine", False) or ( - hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func) + return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False) + + +def is_async_function(func: object) -> bool: + """Return True if the given function seems to be an async function or + an async generator.""" + return iscoroutinefunction(func) or ( + sys.version_info >= (3, 6) and inspect.isasyncgenfunction(func) ) -def getlocation(function, curdir): +def getlocation(function, curdir: Optional[str] = None) -> str: + from _pytest.pathlib import Path + function = get_real_func(function) - fn = py.path.local(inspect.getfile(function)) + fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno - if fn.relto(curdir): - fn = fn.relto(curdir) + if curdir is not None: + try: + relfn = fn.relative_to(curdir) + except ValueError: + pass + else: + return "%s:%d" % (relfn, lineno + 1) return "%s:%d" % (fn, lineno + 1) -def num_mock_patch_args(function): - """ return number of arguments used up by mock arguments (if any) """ +def num_mock_patch_args(function) -> int: + """Return number of arguments used up by mock arguments (if any).""" patchings = getattr(function, "patchings", None) if not patchings: return 0 - mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")] - if any(mock_modules): - sentinels = [m.DEFAULT for m in mock_modules if m is not None] - return len( - [p for p in patchings if not p.attribute_name and p.new in sentinels] - ) - return len(patchings) + mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object()) + ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object()) + + return len( + [ + p + for p in patchings + if not p.attribute_name + and (p.new is mock_sentinel or p.new is ut_mock_sentinel) + ] + ) -def getfuncargnames(function, is_method=False, cls=None): - """Returns the names of a function's mandatory arguments. - This should return the names of all function arguments that: - * Aren't bound to an instance or type as in instance or class methods. - * Don't have default values. - * Aren't bound with functools.partial. - * Aren't replaced with mocks. +def getfuncargnames( + function: Callable[..., Any], + *, + name: str = "", + is_method: bool = False, + cls: Optional[type] = None +) -> Tuple[str, ...]: + """Return the names of a function's mandatory arguments. + + Should return the names of all function arguments that: + * Aren't bound to an instance or type as in instance or class methods. + * Don't have default values. + * Aren't bound with functools.partial. + * Aren't replaced with mocks. The is_method and cls arguments indicate that the function should be treated as a bound method even though it's not unless, only in the case of cls, the function is a static method. - @RonnyPfannschmidt: This function should be refactored when we - revisit fixtures. The fixture mechanism should ask the node for - the fixture names, and not try to obtain directly from the - function object well after collection has occurred. - + The name parameter should be the original name in which the function was collected. """ + # TODO(RonnyPfannschmidt): This function should be refactored when we + # revisit fixtures. The fixture mechanism should ask the node for + # the fixture names, and not try to obtain directly from the + # function object well after collection has occurred. + # The parameters attribute of a Signature object contains an # ordered mapping of parameter names to Parameter instances. This # creates a tuple of the names of the parameters that don't have @@ -154,16 +179,20 @@ def getfuncargnames(function, is_method=False, cls=None): p.name for p in parameters.values() if ( - p.kind is Parameter.POSITIONAL_OR_KEYWORD - or p.kind is Parameter.KEYWORD_ONLY + # TODO: Remove type ignore after https://github.com/python/typeshed/pull/4383 + p.kind is Parameter.POSITIONAL_OR_KEYWORD # type: ignore[unreachable] + or p.kind is Parameter.KEYWORD_ONLY # type: ignore[unreachable] ) and p.default is Parameter.empty ) + if not name: + name = function.__name__ + # If this function should be treated as a bound method even though # it's passed as an unbound method or function, remove the first # parameter name. if is_method or ( - cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod) + cls and not isinstance(cls.__dict__.get(name, None), staticmethod) ): arg_names = arg_names[1:] # Remove any names that will be replaced with mocks. @@ -172,16 +201,21 @@ def getfuncargnames(function, is_method=False, cls=None): return arg_names -@contextmanager -def dummy_context_manager(): - """Context manager that does nothing, useful in situations where you might need an actual context manager or not - depending on some condition. Using this allow to keep the same code""" - yield +if sys.version_info < (3, 7): + + @contextmanager + def nullcontext(): + yield -def get_default_arg_names(function): - # Note: this code intentionally mirrors the code at the beginning of getfuncargnames, - # to get the arguments which were excluded from its result because they had default values +else: + from contextlib import nullcontext as nullcontext # noqa: F401 + + +def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: + # Note: this code intentionally mirrors the code at the beginning of + # getfuncargnames, to get the arguments which were excluded from its result + # because they had default values. return tuple( p.name for p in signature(function).parameters.values() @@ -191,101 +225,63 @@ def get_default_arg_names(function): _non_printable_ascii_translate_table = { - i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127) + i: "\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127) } _non_printable_ascii_translate_table.update( - {ord("\t"): u"\\t", ord("\r"): u"\\r", ord("\n"): u"\\n"} + {ord("\t"): "\\t", ord("\r"): "\\r", ord("\n"): "\\n"} ) -def _translate_non_printable(s): +def _translate_non_printable(s: str) -> str: return s.translate(_non_printable_ascii_translate_table) -if _PY3: - STRING_TYPES = bytes, str - UNICODE_TYPES = six.text_type +STRING_TYPES = bytes, str - if PY35: - def _bytes_to_ascii(val): - return val.decode("ascii", "backslashreplace") - - else: +def _bytes_to_ascii(val: bytes) -> str: + return val.decode("ascii", "backslashreplace") - def _bytes_to_ascii(val): - if val: - # source: http://goo.gl/bGsnwC - encoded_bytes, _ = codecs.escape_encode(val) - return encoded_bytes.decode("ascii") - else: - # empty bytes crashes codecs.escape_encode (#1087) - return "" - def ascii_escaped(val): - """If val is pure ascii, returns it as a str(). Otherwise, escapes - bytes objects into a sequence of escaped bytes: +def ascii_escaped(val: Union[bytes, str]) -> str: + r"""If val is pure ASCII, return it as an str, otherwise, escape + bytes objects into a sequence of escaped bytes: - b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' + b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6' - and escapes unicode objects into a sequence of escaped unicode - ids, e.g.: + and escapes unicode objects into a sequence of escaped unicode + ids, e.g.: - '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944' + r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944' - note: - the obvious "v.decode('unicode-escape')" will return - valid utf-8 unicode if it finds them in bytes, but we - want to return escaped bytes for any byte, even if they match - a utf-8 string. - - """ - if isinstance(val, bytes): - ret = _bytes_to_ascii(val) - else: - ret = val.encode("unicode_escape").decode("ascii") - return _translate_non_printable(ret) - - -else: - STRING_TYPES = six.string_types - UNICODE_TYPES = six.text_type - - def ascii_escaped(val): - """In py2 bytes and str are the same type, so return if it's a bytes - object, return it unchanged if it is a full ascii string, - otherwise escape it into its binary form. - - If it's a unicode string, change the unicode characters into - unicode escapes. - - """ - if isinstance(val, bytes): - try: - ret = val.decode("ascii") - except UnicodeDecodeError: - ret = val.encode("string-escape").decode("ascii") - else: - ret = val.encode("unicode-escape").decode("ascii") - return _translate_non_printable(ret) + Note: + The obvious "v.decode('unicode-escape')" will return + valid UTF-8 unicode if it finds them in bytes, but we + want to return escaped bytes for any byte, even if they match + a UTF-8 string. + """ + if isinstance(val, bytes): + ret = _bytes_to_ascii(val) + else: + ret = val.encode("unicode_escape").decode("ascii") + return _translate_non_printable(ret) -class _PytestWrapper(object): +@attr.s +class _PytestWrapper: """Dummy wrapper around a function object for internal use only. - Used to correctly unwrap the underlying function object - when we are creating fixtures, because we wrap the function object ourselves with a decorator - to issue warnings when the fixture function is called directly. + Used to correctly unwrap the underlying function object when we are + creating fixtures, because we wrap the function object ourselves with a + decorator to issue warnings when the fixture function is called directly. """ - def __init__(self, obj): - self.obj = obj + obj = attr.ib() def get_real_func(obj): - """ gets the real function object of the (possibly) wrapped object by - functools.wraps or functools.partial. - """ + """Get the real function object of the (possibly) wrapped object by + functools.wraps or functools.partial.""" start_obj = obj for i in range(100): # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function @@ -300,6 +296,8 @@ def get_real_func(obj): break obj = new_obj else: + from _pytest._io.saferepr import saferepr + raise ValueError( ("could not find real function of {start}\nstopped at {current}").format( start=saferepr(start_obj), current=saferepr(obj) @@ -311,30 +309,19 @@ def get_real_func(obj): def get_real_method(obj, holder): - """ - Attempts to obtain the real function object that might be wrapping ``obj``, while at the same time - returning a bound method to ``holder`` if the original object was a bound method. - """ + """Attempt to obtain the real function object that might be wrapping + ``obj``, while at the same time returning a bound method to ``holder`` if + the original object was a bound method.""" try: is_method = hasattr(obj, "__func__") obj = get_real_func(obj) - except Exception: + except Exception: # pragma: no cover return obj if is_method and hasattr(obj, "__get__") and callable(obj.__get__): obj = obj.__get__(holder) return obj -def getfslineno(obj): - # xxx let decorators etc specify a sane ordering - obj = get_real_func(obj) - if hasattr(obj, "place_as"): - obj = obj.place_as - fslineno = _pytest._code.getfslineno(obj) - assert isinstance(fslineno[1], int), obj - return fslineno - - def getimfunc(func): try: return func.__func__ @@ -342,13 +329,14 @@ def getimfunc(func): return func -def safe_getattr(object, name, default): - """ Like getattr but return default upon any Exception or any OutcomeException. +def safe_getattr(object: Any, name: str, default: Any) -> Any: + """Like getattr but return default upon any Exception or any OutcomeException. Attribute access can potentially fail for 'evil' Python objects. See issue #214. - It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException - instead of Exception (for more details check #2707) + It catches OutcomeException because of #2490 (issue #580), new outcomes + are derived from BaseException instead of Exception (for more details + check #2707). """ try: return getattr(object, name, default) @@ -356,115 +344,111 @@ def safe_getattr(object, name, default): return default -def safe_isclass(obj): +def safe_isclass(obj: object) -> bool: """Ignore any exception via isinstance on Python 3.""" try: - return isclass(obj) + return inspect.isclass(obj) except Exception: return False -def _is_unittest_unexpected_success_a_failure(): - """Return if the test suite should fail if an @expectedFailure unittest test PASSES. - - From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful: - Changed in version 3.4: Returns False if there were any - unexpectedSuccesses from tests marked with the expectedFailure() decorator. - """ - return sys.version_info >= (3, 4) - - -if _PY3: +if sys.version_info < (3, 5, 2): - def safe_str(v): - """returns v as string""" - return str(v) + def overload(f): # noqa: F811 + return f +if TYPE_CHECKING: + if sys.version_info >= (3, 8): + from typing import final as final + else: + from typing_extensions import final as final +elif sys.version_info >= (3, 8): + from typing import final as final else: - def safe_str(v): - """returns v as string, converting to utf-8 if necessary""" - try: - return str(v) - except UnicodeError: - if not isinstance(v, text_type): - v = text_type(v) - errors = "replace" - return v.encode("utf-8", errors) - - -COLLECT_FAKEMODULE_ATTRIBUTES = ( - "Collector", - "Module", - "Function", - "Instance", - "Session", - "Item", - "Class", - "File", - "_fillfuncargs", -) - - -def _setup_collect_fakemodule(): - from types import ModuleType - import pytest - - pytest.collect = ModuleType("pytest.collect") - pytest.collect.__all__ = [] # used for setns - for attribute in COLLECT_FAKEMODULE_ATTRIBUTES: - setattr(pytest.collect, attribute, getattr(pytest, attribute)) - - -if _PY2: - # Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO. - from py.io import TextIO - - class CaptureIO(TextIO): - @property - def encoding(self): - return getattr(self, "_encoding", "UTF-8") + def final(f): # noqa: F811 + return f +if getattr(attr, "__version_info__", ()) >= (19, 2): + ATTRS_EQ_FIELD = "eq" else: - import io - - class CaptureIO(io.TextIOWrapper): - def __init__(self): - super(CaptureIO, self).__init__( - io.BytesIO(), encoding="UTF-8", newline="", write_through=True - ) - - def getvalue(self): - return self.buffer.getvalue().decode("UTF-8") - - -class FuncargnamesCompatAttr(object): - """ helper class so that Metafunc, Function and FixtureRequest - don't need to each define the "funcargnames" compatibility attribute. - """ - - @property - def funcargnames(self): - """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" - return self.fixturenames - - -if six.PY2: - - def lru_cache(*_, **__): - def dec(fn): - return fn - - return dec + ATTRS_EQ_FIELD = "cmp" +if sys.version_info >= (3, 8): + from functools import cached_property as cached_property else: - from functools import lru_cache # noqa: F401 - -if getattr(attr, "__version_info__", ()) >= (19, 2): - ATTRS_EQ_FIELD = "eq" + class cached_property(Generic[_S, _T]): + __slots__ = ("func", "__doc__") + + def __init__(self, func: Callable[[_S], _T]) -> None: + self.func = func + self.__doc__ = func.__doc__ + + @overload + def __get__( + self, instance: None, owner: Optional["Type[_S]"] = ... + ) -> "cached_property[_S, _T]": + ... + + @overload # noqa: F811 + def __get__( # noqa: F811 + self, instance: _S, owner: Optional["Type[_S]"] = ... + ) -> _T: + ... + + def __get__(self, instance, owner=None): # noqa: F811 + if instance is None: + return self + value = instance.__dict__[self.func.__name__] = self.func(instance) + return value + + +# Sometimes an algorithm needs a dict which yields items in the order in which +# they were inserted when iterated. Since Python 3.7, `dict` preserves +# insertion order. Since `dict` is faster and uses less memory than +# `OrderedDict`, prefer to use it if possible. +if sys.version_info >= (3, 7): + order_preserving_dict = dict else: - ATTRS_EQ_FIELD = "cmp" + from collections import OrderedDict + + order_preserving_dict = OrderedDict + + +# Perform exhaustiveness checking. +# +# Consider this example: +# +# MyUnion = Union[int, str] +# +# def handle(x: MyUnion) -> int { +# if isinstance(x, int): +# return 1 +# elif isinstance(x, str): +# return 2 +# else: +# raise Exception('unreachable') +# +# Now suppose we add a new variant: +# +# MyUnion = Union[int, str, bytes] +# +# After doing this, we must remember ourselves to go and update the handle +# function to handle the new variant. +# +# With `assert_never` we can do better: +# +# // raise Exception('unreachable') +# return assert_never(x) +# +# Now, if we forget to handle the new variant, the type-checker will emit a +# compile-time error, instead of the runtime error we would have gotten +# previously. +# +# This also work for Enums (if you use `is` to compare) and Literals. +def assert_never(value: "NoReturn") -> "NoReturn": + assert False, "Unhandled value: {} ({})".format(value, type(value).__name__) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py index 0737ff9d51c..f89ed37027b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/__init__.py @@ -1,116 +1,220 @@ -# -*- coding: utf-8 -*- -""" command line options, ini-file and conftest.py processing. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Command line options, ini-file and conftest.py processing.""" import argparse +import collections.abc +import contextlib import copy +import enum import inspect import os +import re import shlex import sys import types import warnings +from functools import lru_cache +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import IO +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import TextIO +from typing import Tuple +from typing import Union import attr import py -import six -from packaging.version import Version from pluggy import HookimplMarker from pluggy import HookspecMarker from pluggy import PluginManager import _pytest._code -import _pytest.assertion -import _pytest.hookspec # the extension point definitions -from .exceptions import PrintHelp -from .exceptions import UsageError +import _pytest.deprecated +import _pytest.hookspec +from .exceptions import PrintHelp as PrintHelp +from .exceptions import UsageError as UsageError from .findpaths import determine_setup -from .findpaths import exists -from _pytest import deprecated from _pytest._code import ExceptionInfo from _pytest._code import filter_traceback +from _pytest._io import TerminalWriter +from _pytest.compat import final from _pytest.compat import importlib_metadata -from _pytest.compat import lru_cache -from _pytest.compat import safe_str +from _pytest.compat import TYPE_CHECKING from _pytest.outcomes import fail from _pytest.outcomes import Skipped +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import import_path +from _pytest.pathlib import ImportMode from _pytest.pathlib import Path +from _pytest.store import Store from _pytest.warning_types import PytestConfigWarning +if TYPE_CHECKING: + from typing import Type + + from _pytest._code.code import _TracebackStyle + from _pytest.terminal import TerminalReporter + from .argparsing import Argument + + +_PluggyPlugin = object +"""A type to represent plugin objects. + +Plugins can be any namespace, so we can't narrow it down much, but we use an +alias to make the intent clear. + +Ideally this type would be provided by pluggy itself. +""" + + hookimpl = HookimplMarker("pytest") hookspec = HookspecMarker("pytest") +@final +class ExitCode(enum.IntEnum): + """Encodes the valid exit codes by pytest. + + Currently users and plugins may supply other exit codes as well. + + .. versionadded:: 5.0 + """ + + #: Tests passed. + OK = 0 + #: Tests failed. + TESTS_FAILED = 1 + #: pytest was interrupted. + INTERRUPTED = 2 + #: An internal error got in the way. + INTERNAL_ERROR = 3 + #: pytest was misused. + USAGE_ERROR = 4 + #: pytest couldn't find tests. + NO_TESTS_COLLECTED = 5 + + class ConftestImportFailure(Exception): - def __init__(self, path, excinfo): - Exception.__init__(self, path, excinfo) + def __init__( + self, + path: py.path.local, + excinfo: Tuple["Type[Exception]", Exception, TracebackType], + ) -> None: + super().__init__(path, excinfo) self.path = path self.excinfo = excinfo + def __str__(self) -> str: + return "{}: {} (from {})".format( + self.excinfo[0].__name__, self.excinfo[1], self.path + ) -def main(args=None, plugins=None): - """ return exit code, after performing an in-process test run. - :arg args: list of command line arguments. +def filter_traceback_for_conftest_import_failure( + entry: _pytest._code.TracebackEntry, +) -> bool: + """Filter tracebacks entries which point to pytest internals or importlib. - :arg plugins: list of plugin objects to be auto-registered during - initialization. + Make a special case for importlib because we use it to import test modules and conftest files + in _pytest.pathlib.import_path. """ - from _pytest.main import EXIT_USAGEERROR + return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) + + +def main( + args: Optional[Union[List[str], py.path.local]] = None, + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, +) -> Union[int, ExitCode]: + """Perform an in-process test run. + + :param args: List of command line arguments. + :param plugins: List of plugin objects to be auto-registered during initialization. + :returns: An exit code. + """ try: try: config = _prepareconfig(args, plugins) except ConftestImportFailure as e: exc_info = ExceptionInfo(e.excinfo) - tw = py.io.TerminalWriter(sys.stderr) + tw = TerminalWriter(sys.stderr) tw.line( "ImportError while loading conftest '{e.path}'.".format(e=e), red=True ) - exc_info.traceback = exc_info.traceback.filter(filter_traceback) + exc_info.traceback = exc_info.traceback.filter( + filter_traceback_for_conftest_import_failure + ) exc_repr = ( exc_info.getrepr(style="short", chain=False) if exc_info.traceback else exc_info.exconly() ) - formatted_tb = safe_str(exc_repr) + formatted_tb = str(exc_repr) for line in formatted_tb.splitlines(): tw.line(line.rstrip(), red=True) - return 4 + return ExitCode.USAGE_ERROR else: try: - return config.hook.pytest_cmdline_main(config=config) + ret = config.hook.pytest_cmdline_main( + config=config + ) # type: Union[ExitCode, int] + try: + return ExitCode(ret) + except ValueError: + return ret finally: config._ensure_unconfigure() except UsageError as e: - tw = py.io.TerminalWriter(sys.stderr) + tw = TerminalWriter(sys.stderr) for msg in e.args: tw.line("ERROR: {}\n".format(msg), red=True) - return EXIT_USAGEERROR + return ExitCode.USAGE_ERROR + +def console_main() -> int: + """The CLI entry point of pytest. -class cmdline(object): # compatibility namespace + This function is not meant for programmable use; use `main()` instead. + """ + # https://docs.python.org/3/library/signal.html#note-on-sigpipe + try: + code = main() + sys.stdout.flush() + return code + except BrokenPipeError: + # Python flushes standard streams on exit; redirect remaining output + # to devnull to avoid another BrokenPipeError at shutdown + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + return 1 # Python exits with error code 1 on EPIPE + + +class cmdline: # compatibility namespace main = staticmethod(main) -def filename_arg(path, optname): - """ Argparse type validator for filename arguments. +def filename_arg(path: str, optname: str) -> str: + """Argparse type validator for filename arguments. - :path: path of filename - :optname: name of the option + :path: Path of filename. + :optname: Name of the option. """ if os.path.isdir(path): raise UsageError("{} must be a filename, given: {}".format(optname, path)) return path -def directory_arg(path, optname): +def directory_arg(path: str, optname: str) -> str: """Argparse type validator for directory arguments. - :path: path of directory - :optname: name of the option + :path: Path of directory. + :optname: Name of the option. """ if not os.path.isdir(path): raise UsageError("{} must be a directory, given: {}".format(optname, path)) @@ -140,7 +244,6 @@ default_plugins = essential_plugins + ( "nose", "assertion", "junitxml", - "resultlog", "doctest", "cacheprovider", "freeze_support", @@ -150,34 +253,38 @@ default_plugins = essential_plugins + ( "warnings", "logging", "reports", + "faulthandler", ) builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") -def get_config(args=None, plugins=None): +def get_config( + args: Optional[List[str]] = None, + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, +) -> "Config": # subsequent calls to main will create a fresh instance pluginmanager = PytestPluginManager() config = Config( pluginmanager, invocation_params=Config.InvocationParams( - args=args, plugins=plugins, dir=Path().resolve() + args=args or (), plugins=plugins, dir=Path.cwd(), ), ) if args is not None: # Handle any "-p no:plugin" args. - pluginmanager.consider_preparse(args) + pluginmanager.consider_preparse(args, exclude_only=True) for spec in default_plugins: pluginmanager.import_plugin(spec) + return config -def get_plugin_manager(): - """ - Obtain a new instance of the +def get_plugin_manager() -> "PytestPluginManager": + """Obtain a new instance of the :py:class:`_pytest.config.PytestPluginManager`, with default plugins already loaded. @@ -187,14 +294,16 @@ def get_plugin_manager(): return get_config().pluginmanager -def _prepareconfig(args=None, plugins=None): - warning = None +def _prepareconfig( + args: Optional[Union[py.path.local, List[str]]] = None, + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, +) -> "Config": if args is None: args = sys.argv[1:] elif isinstance(args, py.path.local): args = [str(args)] - elif not isinstance(args, (tuple, list)): - msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})" + elif not isinstance(args, list): + msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})" raise TypeError(msg.format(args, type(args))) config = get_config(args, plugins) @@ -202,50 +311,59 @@ def _prepareconfig(args=None, plugins=None): try: if plugins: for plugin in plugins: - if isinstance(plugin, six.string_types): + if isinstance(plugin, str): pluginmanager.consider_pluginarg(plugin) else: pluginmanager.register(plugin) - if warning: - from _pytest.warnings import _issue_warning_captured - - _issue_warning_captured(warning, hook=config.hook, stacklevel=4) - return pluginmanager.hook.pytest_cmdline_parse( + config = pluginmanager.hook.pytest_cmdline_parse( pluginmanager=pluginmanager, args=args ) + return config except BaseException: config._ensure_unconfigure() raise +@final class PytestPluginManager(PluginManager): - """ - Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific - functionality: + """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with + additional pytest-specific functionality: - * loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and - ``pytest_plugins`` global variables found in plugins being loaded; - * ``conftest.py`` loading during start-up; + * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and + ``pytest_plugins`` global variables found in plugins being loaded. + * ``conftest.py`` loading during start-up. """ - def __init__(self): - super(PytestPluginManager, self).__init__("pytest") - self._conftest_plugins = set() + def __init__(self) -> None: + import _pytest.assertion + + super().__init__("pytest") + # The objects are module objects, only used generically. + self._conftest_plugins = set() # type: Set[types.ModuleType] - # state related to local conftest plugins - self._dirpath2confmods = {} - self._conftestpath2mod = {} - self._confcutdir = None + # State related to local conftest plugins. + self._dirpath2confmods = {} # type: Dict[py.path.local, List[types.ModuleType]] + self._conftestpath2mod = {} # type: Dict[Path, types.ModuleType] + self._confcutdir = None # type: Optional[py.path.local] self._noconftest = False - self._duplicatepaths = set() + self._duplicatepaths = set() # type: Set[py.path.local] + + # plugins that were explicitly skipped with pytest.skip + # list of (module name, skip reason) + # previously we would issue a warning when a plugin was skipped, but + # since we refactored warnings as first citizens of Config, they are + # just stored here to be used later. + self.skipped_plugins = [] # type: List[Tuple[str, str]] self.add_hookspecs(_pytest.hookspec) self.register(self) if os.environ.get("PYTEST_DEBUG"): - err = sys.stderr - encoding = getattr(err, "encoding", "utf8") + err = sys.stderr # type: IO[str] + encoding = getattr(err, "encoding", "utf8") # type: str try: - err = py.io.dupfile(err, encoding=encoding) + err = open( + os.dup(err.fileno()), mode=err.mode, buffering=1, encoding=encoding, + ) except Exception: pass self.trace.root.setwriter(err.write) @@ -253,37 +371,27 @@ class PytestPluginManager(PluginManager): # Config._consider_importhook will set a real object if required. self.rewrite_hook = _pytest.assertion.DummyRewriteHook() - # Used to know when we are importing conftests after the pytest_configure stage + # Used to know when we are importing conftests after the pytest_configure stage. self._configured = False - def addhooks(self, module_or_class): - """ - .. deprecated:: 2.8 - - Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>` - instead. - """ - warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2) - return self.add_hookspecs(module_or_class) - - def parse_hookimpl_opts(self, plugin, name): - # pytest hooks are always prefixed with pytest_ + def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): + # pytest hooks are always prefixed with "pytest_", # so we avoid accessing possibly non-readable attributes - # (see issue #1073) + # (see issue #1073). if not name.startswith("pytest_"): return - # ignore names which can not be hooks + # Ignore names which can not be hooks. if name == "pytest_plugins": return method = getattr(plugin, name) - opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) + opts = super().parse_hookimpl_opts(plugin, name) - # consider only actual functions for hooks (#3775) + # Consider only actual functions for hooks (#3775). if not inspect.isroutine(method): return - # collect unmarked hooks as long as they have the `pytest_' prefix + # Collect unmarked hooks as long as they have the `pytest_' prefix. if opts is None and name.startswith("pytest_"): opts = {} if opts is not None: @@ -295,10 +403,8 @@ class PytestPluginManager(PluginManager): opts.setdefault(name, hasattr(method, name) or name in known_marks) return opts - def parse_hookspec_opts(self, module_or_class, name): - opts = super(PytestPluginManager, self).parse_hookspec_opts( - module_or_class, name - ) + def parse_hookspec_opts(self, module_or_class, name: str): + opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: method = getattr(module_or_class, name) @@ -314,8 +420,10 @@ class PytestPluginManager(PluginManager): } return opts - def register(self, plugin, name=None): - if name in ["pytest_catchlog", "pytest_capturelog"]: + def register( + self, plugin: _PluggyPlugin, name: Optional[str] = None + ) -> Optional[str]: + if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: warnings.warn( PytestConfigWarning( "{} plugin has been merged into the core, " @@ -324,8 +432,8 @@ class PytestPluginManager(PluginManager): ) ) ) - return - ret = super(PytestPluginManager, self).register(plugin, name) + return None + ret = super().register(plugin, name) # type: Optional[str] if ret: self.hook.pytest_plugin_registered.call_historic( kwargs=dict(plugin=plugin, manager=self) @@ -335,17 +443,19 @@ class PytestPluginManager(PluginManager): self.consider_module(plugin) return ret - def getplugin(self, name): - # support deprecated naming because plugins (xdist e.g.) use it - return self.get_plugin(name) + def getplugin(self, name: str): + # Support deprecated naming because plugins (xdist e.g.) use it. + plugin = self.get_plugin(name) # type: Optional[_PluggyPlugin] + return plugin - def hasplugin(self, name): - """Return True if the plugin with the given name is registered.""" + def hasplugin(self, name: str) -> bool: + """Return whether a plugin with the given name is registered.""" return bool(self.get_plugin(name)) - def pytest_configure(self, config): + def pytest_configure(self, config: "Config") -> None: + """:meta private:""" # XXX now that the pluginmanager exposes hookimpl(tryfirst...) - # we should remove tryfirst/trylast as markers + # we should remove tryfirst/trylast as markers. config.addinivalue_line( "markers", "tryfirst: mark a hook implementation function such that the " @@ -359,15 +469,15 @@ class PytestPluginManager(PluginManager): self._configured = True # - # internal API for local conftest plugin handling + # Internal API for local conftest plugin handling. # - def _set_initial_conftests(self, namespace): - """ load initial conftest files given a preparsed "namespace". - As conftest files may add their own command line options - which have arguments ('--my-opt somepath') we might get some - false positives. All builtin and 3rd party plugins will have - been loaded, however, so common options will not confuse our logic - here. + def _set_initial_conftests(self, namespace: argparse.Namespace) -> None: + """Load initial conftest files given a preparsed "namespace". + + As conftest files may add their own command line options which have + arguments ('--my-opt somepath') we might get some false positives. + All builtin and 3rd party plugins will have been loaded, however, so + common options will not confuse our logic here. """ current = py.path.local() self._confcutdir = ( @@ -379,29 +489,33 @@ class PytestPluginManager(PluginManager): self._using_pyargs = namespace.pyargs testpaths = namespace.file_or_dir foundanchor = False - for path in testpaths: - path = str(path) + for testpath in testpaths: + path = str(testpath) # remove node-id syntax i = path.find("::") if i != -1: path = path[:i] anchor = current.join(path, abs=1) - if exists(anchor): # we found some file object - self._try_load_conftest(anchor) + if anchor.exists(): # we found some file object + self._try_load_conftest(anchor, namespace.importmode) foundanchor = True if not foundanchor: - self._try_load_conftest(current) + self._try_load_conftest(current, namespace.importmode) - def _try_load_conftest(self, anchor): - self._getconftestmodules(anchor) + def _try_load_conftest( + self, anchor: py.path.local, importmode: Union[str, ImportMode] + ) -> None: + self._getconftestmodules(anchor, importmode) # let's also consider test* subdirs if anchor.check(dir=1): for x in anchor.listdir("test*"): if x.check(dir=1): - self._getconftestmodules(x) + self._getconftestmodules(x, importmode) @lru_cache(maxsize=128) - def _getconftestmodules(self, path): + def _getconftestmodules( + self, path: py.path.local, importmode: Union[str, ImportMode], + ) -> List[types.ModuleType]: if self._noconftest: return [] @@ -410,31 +524,24 @@ class PytestPluginManager(PluginManager): else: directory = path - if six.PY2: # py2 is not using lru_cache. - try: - return self._dirpath2confmods[directory] - except KeyError: - pass - - # XXX these days we may rather want to use config.rootdir + # XXX these days we may rather want to use config.rootpath # and allow users to opt into looking into the rootdir parent - # directories instead of requiring to specify confcutdir + # directories instead of requiring to specify confcutdir. clist = [] - for parent in directory.realpath().parts(): + for parent in directory.parts(): if self._confcutdir and self._confcutdir.relto(parent): continue conftestpath = parent.join("conftest.py") if conftestpath.isfile(): - # Use realpath to avoid loading the same conftest twice - # with build systems that create build directories containing - # symlinks to actual files. - mod = self._importconftest(conftestpath.realpath()) + mod = self._importconftest(conftestpath, importmode) clist.append(mod) self._dirpath2confmods[directory] = clist return clist - def _rget_with_confmod(self, name, path): - modules = self._getconftestmodules(path) + def _rget_with_confmod( + self, name: str, path: py.path.local, importmode: Union[str, ImportMode], + ) -> Tuple[types.ModuleType, Any]: + modules = self._getconftestmodules(path, importmode) for mod in reversed(modules): try: return mod, getattr(mod, name) @@ -442,57 +549,77 @@ class PytestPluginManager(PluginManager): continue raise KeyError(name) - def _importconftest(self, conftestpath): - try: - return self._conftestpath2mod[conftestpath] - except KeyError: - pkgpath = conftestpath.pypkgpath() - if pkgpath is None: - _ensure_removed_sysmodule(conftestpath.purebasename) - try: - mod = conftestpath.pyimport() - if ( - hasattr(mod, "pytest_plugins") - and self._configured - and not self._using_pyargs - ): - from _pytest.deprecated import ( - PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST, - ) + def _importconftest( + self, conftestpath: py.path.local, importmode: Union[str, ImportMode], + ) -> types.ModuleType: + # Use a resolved Path object as key to avoid loading the same conftest + # twice with build systems that create build directories containing + # symlinks to actual files. + # Using Path().resolve() is better than py.path.realpath because + # it resolves to the correct path/drive in case-insensitive file systems (#5792) + key = Path(str(conftestpath)).resolve() - fail( - PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format( - conftestpath, self._confcutdir - ), - pytrace=False, - ) - except Exception: - raise ConftestImportFailure(conftestpath, sys.exc_info()) - - self._conftest_plugins.add(mod) - self._conftestpath2mod[conftestpath] = mod - dirpath = conftestpath.dirpath() - if dirpath in self._dirpath2confmods: - for path, mods in self._dirpath2confmods.items(): - if path and path.relto(dirpath) or path == dirpath: - assert mod not in mods - mods.append(mod) - self.trace("loaded conftestmodule %r" % (mod)) - self.consider_conftest(mod) - return mod + with contextlib.suppress(KeyError): + return self._conftestpath2mod[key] + + pkgpath = conftestpath.pypkgpath() + if pkgpath is None: + _ensure_removed_sysmodule(conftestpath.purebasename) + + try: + mod = import_path(conftestpath, mode=importmode) + except Exception as e: + assert e.__traceback__ is not None + exc_info = (type(e), e, e.__traceback__) + raise ConftestImportFailure(conftestpath, exc_info) from e + + self._check_non_top_pytest_plugins(mod, conftestpath) + + self._conftest_plugins.add(mod) + self._conftestpath2mod[key] = mod + dirpath = conftestpath.dirpath() + if dirpath in self._dirpath2confmods: + for path, mods in self._dirpath2confmods.items(): + if path and path.relto(dirpath) or path == dirpath: + assert mod not in mods + mods.append(mod) + self.trace("loading conftestmodule {!r}".format(mod)) + self.consider_conftest(mod) + return mod + + def _check_non_top_pytest_plugins( + self, mod: types.ModuleType, conftestpath: py.path.local, + ) -> None: + if ( + hasattr(mod, "pytest_plugins") + and self._configured + and not self._using_pyargs + ): + msg = ( + "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n" + "It affects the entire test suite instead of just below the conftest as expected.\n" + " {}\n" + "Please move it to a top level conftest file at the rootdir:\n" + " {}\n" + "For more information, visit:\n" + " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" + ) + fail(msg.format(conftestpath, self._confcutdir), pytrace=False) # # API for bootstrapping plugin loading # # - def consider_preparse(self, args): + def consider_preparse( + self, args: Sequence[str], *, exclude_only: bool = False + ) -> None: i = 0 n = len(args) while i < n: opt = args[i] i += 1 - if isinstance(opt, six.string_types): + if isinstance(opt, str): if opt == "-p": try: parg = args[i] @@ -503,15 +630,17 @@ class PytestPluginManager(PluginManager): parg = opt[2:] else: continue + if exclude_only and not parg.startswith("no:"): + continue self.consider_pluginarg(parg) - def consider_pluginarg(self, arg): + def consider_pluginarg(self, arg: str) -> None: if arg.startswith("no:"): name = arg[3:] if name in essential_plugins: raise UsageError("plugin %s cannot be disabled" % name) - # PR #4304 : remove stepwise if cacheprovider is blocked + # PR #4304: remove stepwise if cacheprovider is blocked. if name == "cacheprovider": self.set_blocked("stepwise") self.set_blocked("pytest_stepwise") @@ -530,33 +659,35 @@ class PytestPluginManager(PluginManager): del self._name2plugin["pytest_" + name] self.import_plugin(arg, consider_entry_points=True) - def consider_conftest(self, conftestmodule): + def consider_conftest(self, conftestmodule: types.ModuleType) -> None: self.register(conftestmodule, name=conftestmodule.__file__) - def consider_env(self): + def consider_env(self) -> None: self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) - def consider_module(self, mod): + def consider_module(self, mod: types.ModuleType) -> None: self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) - def _import_plugin_specs(self, spec): + def _import_plugin_specs( + self, spec: Union[None, types.ModuleType, str, Sequence[str]] + ) -> None: plugins = _get_plugin_specs_as_list(spec) for import_spec in plugins: self.import_plugin(import_spec) - def import_plugin(self, modname, consider_entry_points=False): - """ - Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point - names are also considered to find a plugin. + def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: + """Import a plugin with ``modname``. + + If ``consider_entry_points`` is True, entry point names are also + considered to find a plugin. """ - # most often modname refers to builtin modules, e.g. "pytester", + # Most often modname refers to builtin modules, e.g. "pytester", # "terminal" or "capture". Those plugins are registered under their # basename for historic purposes but must be imported with the # _pytest prefix. - assert isinstance(modname, six.string_types), ( + assert isinstance(modname, str), ( "module name as text required, got %r" % modname ) - modname = str(modname) if self.is_blocked(modname) or self.get_plugin(modname) is not None: return @@ -571,56 +702,47 @@ class PytestPluginManager(PluginManager): try: __import__(importspec) except ImportError as e: - new_exc_message = 'Error importing plugin "%s": %s' % ( - modname, - safe_str(e.args[0]), - ) - new_exc = ImportError(new_exc_message) - tb = sys.exc_info()[2] - - six.reraise(ImportError, new_exc, tb) + raise ImportError( + 'Error importing plugin "{}": {}'.format(modname, str(e.args[0])) + ).with_traceback(e.__traceback__) from e except Skipped as e: - from _pytest.warnings import _issue_warning_captured - - _issue_warning_captured( - PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)), - self.hook, - stacklevel=1, - ) + self.skipped_plugins.append((modname, e.msg or "")) else: mod = sys.modules[importspec] self.register(mod, modname) -def _get_plugin_specs_as_list(specs): - """ - Parses a list of "plugin specs" and returns a list of plugin names. - - Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in - which case it is returned as a list. Specs can also be `None` in which case an - empty list is returned. - """ - if specs is not None and not isinstance(specs, types.ModuleType): - if isinstance(specs, six.string_types): - specs = specs.split(",") if specs else [] - if not isinstance(specs, (list, tuple)): - raise UsageError( - "Plugin specs must be a ','-separated string or a " - "list/tuple of strings for plugin names. Given: %r" % specs - ) +def _get_plugin_specs_as_list( + specs: Union[None, types.ModuleType, str, Sequence[str]] +) -> List[str]: + """Parse a plugins specification into a list of plugin names.""" + # None means empty. + if specs is None: + return [] + # Workaround for #3899 - a submodule which happens to be called "pytest_plugins". + if isinstance(specs, types.ModuleType): + return [] + # Comma-separated list. + if isinstance(specs, str): + return specs.split(",") if specs else [] + # Direct specification. + if isinstance(specs, collections.abc.Sequence): return list(specs) - return [] + raise UsageError( + "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r" + % specs + ) -def _ensure_removed_sysmodule(modname): +def _ensure_removed_sysmodule(modname: str) -> None: try: del sys.modules[modname] except KeyError: pass -class Notset(object): +class Notset: def __repr__(self): return "<NOTSET>" @@ -628,11 +750,12 @@ class Notset(object): notset = Notset() -def _iter_rewritable_modules(package_files): - """ - Given an iterable of file names in a source distribution, return the "names" that should - be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should - be added as "pytest_mock" in the assertion rewrite mechanism. +def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: + """Given an iterable of file names in a source distribution, return the "names" that should + be marked for assertion rewrite. + + For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in + the assertion rewrite mechanism. This function has to deal with dist-info based distributions and egg based distributions (which are still very much in use for "editable" installs). @@ -676,11 +799,11 @@ def _iter_rewritable_modules(package_files): yield package_name if not seen_some: - # at this point we did not find any packages or modules suitable for assertion + # At this point we did not find any packages or modules suitable for assertion # rewriting, so we try again by stripping the first path component (to account for - # "src" based source trees for example) - # this approach lets us have the common case continue to be fast, as egg-distributions - # are rarer + # "src" based source trees for example). + # This approach lets us have the common case continue to be fast, as egg-distributions + # are rarer. new_package_files = [] for fn in package_files: parts = fn.split("/") @@ -688,91 +811,177 @@ def _iter_rewritable_modules(package_files): if new_fn: new_package_files.append(new_fn) if new_package_files: - for _module in _iter_rewritable_modules(new_package_files): - yield _module + yield from _iter_rewritable_modules(new_package_files) -class Config(object): - """ - Access to configuration values, pluginmanager and plugin hooks. +def _args_converter(args: Iterable[str]) -> Tuple[str, ...]: + return tuple(args) - :ivar PytestPluginManager pluginmanager: the plugin manager handles plugin registration and hook invocation. - :ivar argparse.Namespace option: access to command line option as attributes. +@final +class Config: + """Access to configuration values, pluginmanager and plugin hooks. - :ivar InvocationParams invocation_params: + :param PytestPluginManager pluginmanager: - Object containing the parameters regarding the ``pytest.main`` + :param InvocationParams invocation_params: + Object containing parameters regarding the :func:`pytest.main` invocation. - Contains the followinig read-only attributes: - * ``args``: list of command-line arguments as passed to ``pytest.main()``. - * ``plugins``: list of extra plugins, might be None - * ``dir``: directory where ``pytest.main()`` was invoked from. """ + @final @attr.s(frozen=True) - class InvocationParams(object): - """Holds parameters passed during ``pytest.main()`` + class InvocationParams: + """Holds parameters passed during :func:`pytest.main`. + + The object attributes are read-only. + + .. versionadded:: 5.1 .. note:: - Currently the environment variable PYTEST_ADDOPTS is also handled by - pytest implicitly, not being part of the invocation. + Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` + ini option are handled by pytest, not being included in the ``args`` attribute. Plugins accessing ``InvocationParams`` must be aware of that. """ - args = attr.ib() - plugins = attr.ib() - dir = attr.ib() + args = attr.ib(type=Tuple[str, ...], converter=_args_converter) + """The command-line arguments as passed to :func:`pytest.main`. + + :type: Tuple[str, ...] + """ + plugins = attr.ib(type=Optional[Sequence[Union[str, _PluggyPlugin]]]) + """Extra plugins, might be `None`. + + :type: Optional[Sequence[Union[str, plugin]]] + """ + dir = attr.ib(type=Path) + """The directory from which :func:`pytest.main` was invoked. + + :type: pathlib.Path + """ - def __init__(self, pluginmanager, invocation_params=None, *args): + def __init__( + self, + pluginmanager: PytestPluginManager, + *, + invocation_params: Optional[InvocationParams] = None + ) -> None: from .argparsing import Parser, FILE_OR_DIR if invocation_params is None: invocation_params = self.InvocationParams( - args=(), plugins=None, dir=Path().resolve() + args=(), plugins=None, dir=Path.cwd() ) - #: access to command line option as attributes. - #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead self.option = argparse.Namespace() + """Access to command line option as attributes. + + :type: argparse.Namespace + """ self.invocation_params = invocation_params + """The parameters with which pytest was invoked. + + :type: InvocationParams + """ _a = FILE_OR_DIR self._parser = Parser( - usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a), + usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a), processopt=self._processopt, ) - #: a pluginmanager instance self.pluginmanager = pluginmanager + """The plugin manager handles plugin registration and hook invocation. + + :type: PytestPluginManager + """ + self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook - self._inicache = {} - self._override_ini = () - self._opt2dest = {} - self._cleanup = [] + self._inicache = {} # type: Dict[str, Any] + self._override_ini = () # type: Sequence[str] + self._opt2dest = {} # type: Dict[str, str] + self._cleanup = [] # type: List[Callable[[], None]] + # A place where plugins can store information on the config for their + # own use. Currently only intended for internal plugins. + self._store = Store() self.pluginmanager.register(self, "pytestconfig") self._configured = False - self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser)) + self.hook.pytest_addoption.call_historic( + kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) + ) + + if TYPE_CHECKING: + from _pytest.cacheprovider import Cache + + self.cache = None # type: Optional[Cache] @property - def invocation_dir(self): - """Backward compatibility""" + def invocation_dir(self) -> py.path.local: + """The directory from which pytest was invoked. + + Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`, + which is a :class:`pathlib.Path`. + + :type: py.path.local + """ return py.path.local(str(self.invocation_params.dir)) - def add_cleanup(self, func): - """ Add a function to be called when the config object gets out of + @property + def rootpath(self) -> Path: + """The path to the :ref:`rootdir <rootdir>`. + + :type: pathlib.Path + + .. versionadded:: 6.1 + """ + return self._rootpath + + @property + def rootdir(self) -> py.path.local: + """The path to the :ref:`rootdir <rootdir>`. + + Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`. + + :type: py.path.local + """ + return py.path.local(str(self.rootpath)) + + @property + def inipath(self) -> Optional[Path]: + """The path to the :ref:`configfile <configfiles>`. + + :type: Optional[pathlib.Path] + + .. versionadded:: 6.1 + """ + return self._inipath + + @property + def inifile(self) -> Optional[py.path.local]: + """The path to the :ref:`configfile <configfiles>`. + + Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. + + :type: Optional[py.path.local] + """ + return py.path.local(str(self.inipath)) if self.inipath else None + + def add_cleanup(self, func: Callable[[], None]) -> None: + """Add a function to be called when the config object gets out of use (usually coninciding with pytest_unconfigure).""" self._cleanup.append(func) - def _do_configure(self): + def _do_configure(self) -> None: assert not self._configured self._configured = True - self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) + with warnings.catch_warnings(): + warnings.simplefilter("default") + self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) - def _ensure_unconfigure(self): + def _ensure_unconfigure(self) -> None: if self._configured: self._configured = False self.hook.pytest_unconfigure(config=self) @@ -781,10 +990,15 @@ class Config(object): fin = self._cleanup.pop() fin() - def get_terminal_writer(self): - return self.pluginmanager.get_plugin("terminalreporter")._tw + def get_terminal_writer(self) -> TerminalWriter: + terminalreporter = self.pluginmanager.get_plugin( + "terminalreporter" + ) # type: TerminalReporter + return terminalreporter._tw - def pytest_cmdline_parse(self, pluginmanager, args): + def pytest_cmdline_parse( + self, pluginmanager: PytestPluginManager, args: List[str] + ) -> "Config": try: self.parse(args) except UsageError: @@ -808,9 +1022,13 @@ class Config(object): return self - def notify_exception(self, excinfo, option=None): + def notify_exception( + self, + excinfo: ExceptionInfo[BaseException], + option: Optional[argparse.Namespace] = None, + ) -> None: if option and getattr(option, "fulltrace", False): - style = "long" + style = "long" # type: _TracebackStyle else: style = "native" excrepr = excinfo.getrepr( @@ -822,16 +1040,16 @@ class Config(object): sys.stderr.write("INTERNALERROR> %s\n" % line) sys.stderr.flush() - def cwd_relative_nodeid(self, nodeid): - # nodeid's are relative to the rootpath, compute relative to cwd - if self.invocation_dir != self.rootdir: - fullpath = self.rootdir.join(nodeid) - nodeid = self.invocation_dir.bestrelpath(fullpath) + def cwd_relative_nodeid(self, nodeid: str) -> str: + # nodeid's are relative to the rootpath, compute relative to cwd. + if self.invocation_params.dir != self.rootpath: + fullpath = self.rootpath / nodeid + nodeid = bestrelpath(self.invocation_params.dir, fullpath) return nodeid @classmethod - def fromdictargs(cls, option_dict, args): - """ constructor useable for subprocesses. """ + def fromdictargs(cls, option_dict, args) -> "Config": + """Constructor usable for subprocesses.""" config = get_config(args) config.option.__dict__.update(option_dict) config.parse(args, addopts=False) @@ -839,36 +1057,44 @@ class Config(object): config.pluginmanager.consider_pluginarg(x) return config - def _processopt(self, opt): + def _processopt(self, opt: "Argument") -> None: for name in opt._short_opts + opt._long_opts: self._opt2dest[name] = opt.dest - if hasattr(opt, "default") and opt.dest: + if hasattr(opt, "default"): if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) @hookimpl(trylast=True) - def pytest_load_initial_conftests(self, early_config): + def pytest_load_initial_conftests(self, early_config: "Config") -> None: self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) - def _initini(self, args): + def _initini(self, args: Sequence[str]) -> None: ns, unknown_args = self._parser.parse_known_and_unknown_args( args, namespace=copy.copy(self.option) ) - r = determine_setup( + rootpath, inipath, inicfg = determine_setup( ns.inifilename, ns.file_or_dir + unknown_args, rootdir_cmd_arg=ns.rootdir or None, config=self, ) - self.rootdir, self.inifile, self.inicfg = r - self._parser.extra_info["rootdir"] = self.rootdir - self._parser.extra_info["inifile"] = self.inifile + self._rootpath = rootpath + self._inipath = inipath + self.inicfg = inicfg + self._parser.extra_info["rootdir"] = str(self.rootpath) + self._parser.extra_info["inifile"] = str(self.inipath) self._parser.addini("addopts", "extra command line options", "args") self._parser.addini("minversion", "minimally required pytest version") + self._parser.addini( + "required_plugins", + "plugins that must be present for pytest to run", + type="args", + default=[], + ) self._override_ini = ns.override_ini or () - def _consider_importhook(self, args): + def _consider_importhook(self, args: Sequence[str]) -> None: """Install the PEP 302 import hook if using assertion rewriting. Needs to parse the --assert=<mode> option from the commandline @@ -878,20 +1104,20 @@ class Config(object): ns, unknown_args = self._parser.parse_known_and_unknown_args(args) mode = getattr(ns, "assertmode", "plain") if mode == "rewrite": + import _pytest.assertion + try: hook = _pytest.assertion.install_importhook(self) except SystemError: mode = "plain" else: self._mark_plugins_for_rewrite(hook) - _warn_about_missing_assertion(mode) + self._warn_about_missing_assertion(mode) - def _mark_plugins_for_rewrite(self, hook): - """ - Given an importhook, mark for rewrite any top-level + def _mark_plugins_for_rewrite(self, hook) -> None: + """Given an importhook, mark for rewrite any top-level modules or packages in the distribution package for - all pytest plugins. - """ + all pytest plugins.""" self.pluginmanager.rewrite_hook = hook if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): @@ -908,19 +1134,19 @@ class Config(object): for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) - def _validate_args(self, args, via): + def _validate_args(self, args: List[str], via: str) -> List[str]: """Validate known args.""" - self._parser._config_source_hint = via + self._parser._config_source_hint = via # type: ignore try: self._parser.parse_known_and_unknown_args( args, namespace=copy.copy(self.option) ) finally: - del self._parser._config_source_hint + del self._parser._config_source_hint # type: ignore return args - def _preparse(self, args, addopts=True): + def _preparse(self, args: List[str], addopts: bool = True) -> None: if addopts: env_addopts = os.environ.get("PYTEST_ADDOPTS", "") if len(env_addopts): @@ -934,109 +1160,211 @@ class Config(object): self._validate_args(self.getini("addopts"), "via addopts config") + args ) + self.known_args_namespace = self._parser.parse_known_args( + args, namespace=copy.copy(self.option) + ) self._checkversion() self._consider_importhook(args) - self.pluginmanager.consider_preparse(args) + self.pluginmanager.consider_preparse(args, exclude_only=False) if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): # Don't autoload from setuptools entry point. Only explicitly specified # plugins are going to be loaded. self.pluginmanager.load_setuptools_entrypoints("pytest11") self.pluginmanager.consider_env() - self.known_args_namespace = ns = self._parser.parse_known_args( - args, namespace=copy.copy(self.option) + + self.known_args_namespace = self._parser.parse_known_args( + args, namespace=copy.copy(self.known_args_namespace) ) - if self.known_args_namespace.confcutdir is None and self.inifile: - confcutdir = py.path.local(self.inifile).dirname + + self._validate_plugins() + self._warn_about_skipped_plugins() + + if self.known_args_namespace.confcutdir is None and self.inipath is not None: + confcutdir = str(self.inipath.parent) self.known_args_namespace.confcutdir = confcutdir try: self.hook.pytest_load_initial_conftests( early_config=self, args=args, parser=self._parser ) - except ConftestImportFailure: - e = sys.exc_info()[1] - if ns.help or ns.version: + except ConftestImportFailure as e: + if self.known_args_namespace.help or self.known_args_namespace.version: # we don't want to prevent --help/--version to work # so just let is pass and print a warning at the end - from _pytest.warnings import _issue_warning_captured - - _issue_warning_captured( + self.issue_config_time_warning( PytestConfigWarning( "could not load initial conftests: {}".format(e.path) ), - self.hook, stacklevel=2, ) else: raise - def _checkversion(self): + @hookimpl(hookwrapper=True) + def pytest_collection(self) -> Generator[None, None, None]: + """Validate invalid ini keys after collection is done so we take in account + options added by late-loading conftest files.""" + yield + self._validate_config_options() + + def _checkversion(self) -> None: import pytest minver = self.inicfg.get("minversion", None) if minver: + # Imported lazily to improve start-up time. + from packaging.version import Version + + if not isinstance(minver, str): + raise pytest.UsageError( + "%s: 'minversion' must be a single value" % self.inipath + ) + if Version(minver) > Version(pytest.__version__): raise pytest.UsageError( - "%s:%d: requires pytest-%s, actual pytest-%s'" - % ( - self.inicfg.config.path, - self.inicfg.lineof("minversion"), - minver, - pytest.__version__, - ) + "%s: 'minversion' requires pytest-%s, actual pytest-%s'" + % (self.inipath, minver, pytest.__version__,) ) - def parse(self, args, addopts=True): - # parse given cmdline arguments into this config object. + def _validate_config_options(self) -> None: + for key in sorted(self._get_unknown_ini_keys()): + self._warn_or_fail_if_strict("Unknown config option: {}\n".format(key)) + + def _validate_plugins(self) -> None: + required_plugins = sorted(self.getini("required_plugins")) + if not required_plugins: + return + + # Imported lazily to improve start-up time. + from packaging.version import Version + from packaging.requirements import InvalidRequirement, Requirement + + plugin_info = self.pluginmanager.list_plugin_distinfo() + plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} + + missing_plugins = [] + for required_plugin in required_plugins: + try: + spec = Requirement(required_plugin) + except InvalidRequirement: + missing_plugins.append(required_plugin) + continue + + if spec.name not in plugin_dist_info: + missing_plugins.append(required_plugin) + elif Version(plugin_dist_info[spec.name]) not in spec.specifier: + missing_plugins.append(required_plugin) + + if missing_plugins: + raise UsageError( + "Missing required plugins: {}".format(", ".join(missing_plugins)), + ) + + def _warn_or_fail_if_strict(self, message: str) -> None: + if self.known_args_namespace.strict_config: + raise UsageError(message) + + self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) + + def _get_unknown_ini_keys(self) -> List[str]: + parser_inicfg = self._parser._inidict + return [name for name in self.inicfg if name not in parser_inicfg] + + def parse(self, args: List[str], addopts: bool = True) -> None: + # Parse given cmdline arguments into this config object. assert not hasattr( self, "args" ), "can only parse cmdline args at most once per Config object" - self._origargs = args self.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=self.pluginmanager) ) self._preparse(args, addopts=addopts) # XXX deprecated hook: self.hook.pytest_cmdline_preparse(config=self, args=args) - self._parser.after_preparse = True + self._parser.after_preparse = True # type: ignore try: args = self._parser.parse_setoption( args, self.option, namespace=self.option ) if not args: - if self.invocation_dir == self.rootdir: + if self.invocation_params.dir == self.rootpath: args = self.getini("testpaths") if not args: - args = [str(self.invocation_dir)] + args = [str(self.invocation_params.dir)] self.args = args except PrintHelp: pass - def addinivalue_line(self, name, line): - """ add a line to an ini-file option. The option must have been - declared but might not yet be set in which case the line becomes the - the first line in its value. """ + def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: + """Issue and handle a warning during the "configure" stage. + + During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` + function because it is not possible to have hookwrappers around ``pytest_configure``. + + This function is mainly intended for plugins that need to issue warnings during + ``pytest_configure`` (or similar stages). + + :param warning: The warning instance. + :param stacklevel: stacklevel forwarded to warnings.warn. + """ + if self.pluginmanager.is_blocked("warnings"): + return + + cmdline_filters = self.known_args_namespace.pythonwarnings or [] + config_filters = self.getini("filterwarnings") + + with warnings.catch_warnings(record=True) as records: + warnings.simplefilter("always", type(warning)) + apply_warning_filters(config_filters, cmdline_filters) + warnings.warn(warning, stacklevel=stacklevel) + + if records: + frame = sys._getframe(stacklevel - 1) + location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name + self.hook.pytest_warning_captured.call_historic( + kwargs=dict( + warning_message=records[0], + when="config", + item=None, + location=location, + ) + ) + self.hook.pytest_warning_recorded.call_historic( + kwargs=dict( + warning_message=records[0], + when="config", + nodeid="", + location=location, + ) + ) + + def addinivalue_line(self, name: str, line: str) -> None: + """Add a line to an ini-file option. The option must have been + declared but might not yet be set in which case the line becomes + the first line in its value.""" x = self.getini(name) assert isinstance(x, list) x.append(line) # modifies the cached list inline - def getini(self, name): - """ return configuration value from an :ref:`ini file <inifiles>`. If the - specified name hasn't been registered through a prior - :py:func:`parser.addini <_pytest.config.Parser.addini>` - call (usually from a plugin), a ValueError is raised. """ + def getini(self, name: str): + """Return configuration value from an :ref:`ini file <configfiles>`. + + If the specified name hasn't been registered through a prior + :py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>` + call (usually from a plugin), a ValueError is raised. + """ try: return self._inicache[name] except KeyError: self._inicache[name] = val = self._getini(name) return val - def _getini(self, name): + def _getini(self, name: str): try: description, type, default = self._parser._inidict[name] - except KeyError: - raise ValueError("unknown configuration value: %r" % (name,)) - value = self._get_override_ini_value(name) - if value is None: + except KeyError as e: + raise ValueError("unknown configuration value: {!r}".format(name)) from e + override_value = self._get_override_ini_value(name) + if override_value is None: try: value = self.inicfg[name] except KeyError: @@ -1045,58 +1373,86 @@ class Config(object): if type is None: return "" return [] + else: + value = override_value + # Coerce the values based on types. + # + # Note: some coercions are only required if we are reading from .ini files, because + # the file format doesn't contain type information, but when reading from toml we will + # get either str or list of str values (see _parse_ini_config_from_pyproject_toml). + # For example: + # + # ini: + # a_line_list = "tests acceptance" + # in this case, we need to split the string to obtain a list of strings. + # + # toml: + # a_line_list = ["tests", "acceptance"] + # in this case, we already have a list ready to use. + # if type == "pathlist": - dp = py.path.local(self.inicfg.config.path).dirpath() - values = [] - for relpath in shlex.split(value): - values.append(dp.join(relpath, abs=True)) - return values + # TODO: This assert is probably not valid in all cases. + assert self.inipath is not None + dp = self.inipath.parent + input_values = shlex.split(value) if isinstance(value, str) else value + return [py.path.local(str(dp / x)) for x in input_values] elif type == "args": - return shlex.split(value) + return shlex.split(value) if isinstance(value, str) else value elif type == "linelist": - return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] + if isinstance(value, str): + return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] + else: + return value elif type == "bool": - return bool(_strtobool(value.strip())) + return _strtobool(str(value).strip()) else: assert type is None return value - def _getconftest_pathlist(self, name, path): + def _getconftest_pathlist( + self, name: str, path: py.path.local + ) -> Optional[List[py.path.local]]: try: - mod, relroots = self.pluginmanager._rget_with_confmod(name, path) + mod, relroots = self.pluginmanager._rget_with_confmod( + name, path, self.getoption("importmode") + ) except KeyError: return None modpath = py.path.local(mod.__file__).dirpath() - values = [] + values = [] # type: List[py.path.local] for relroot in relroots: if not isinstance(relroot, py.path.local): - relroot = relroot.replace("/", py.path.local.sep) + relroot = relroot.replace("/", os.sep) relroot = modpath.join(relroot, abs=True) values.append(relroot) return values - def _get_override_ini_value(self, name): + def _get_override_ini_value(self, name: str) -> Optional[str]: value = None - # override_ini is a list of "ini=value" options - # always use the last item if multiple values are set for same ini-name, - # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2 + # override_ini is a list of "ini=value" options. + # Always use the last item if multiple values are set for same ini-name, + # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2. for ini_config in self._override_ini: try: key, user_ini_value = ini_config.split("=", 1) - except ValueError: - raise UsageError("-o/--override-ini expects option=value style.") + except ValueError as e: + raise UsageError( + "-o/--override-ini expects option=value style (got: {!r}).".format( + ini_config + ) + ) from e else: if key == name: value = user_ini_value return value - def getoption(self, name, default=notset, skip=False): - """ return command line option value. + def getoption(self, name: str, default=notset, skip: bool = False): + """Return command line option value. - :arg name: name of the option. You may also specify + :param name: Name of the option. You may also specify the literal ``--OPT`` option instead of the "dest" option name. - :arg default: default value if no option of that name exists. - :arg skip: if True raise pytest.skip if option does not exists + :param default: Default value if no option of that name exists. + :param skip: If True, raise pytest.skip if option does not exists or has a None value. """ name = self._opt2dest.get(name, name) @@ -1105,99 +1461,145 @@ class Config(object): if val is None and skip: raise AttributeError(name) return val - except AttributeError: + except AttributeError as e: if default is not notset: return default if skip: import pytest - pytest.skip("no %r option found" % (name,)) - raise ValueError("no option named %r" % (name,)) + pytest.skip("no {!r} option found".format(name)) + raise ValueError("no option named {!r}".format(name)) from e - def getvalue(self, name, path=None): - """ (deprecated, use getoption()) """ + def getvalue(self, name: str, path=None): + """Deprecated, use getoption() instead.""" return self.getoption(name) - def getvalueorskip(self, name, path=None): - """ (deprecated, use getoption(skip=True)) """ + def getvalueorskip(self, name: str, path=None): + """Deprecated, use getoption(skip=True) instead.""" return self.getoption(name, skip=True) + def _warn_about_missing_assertion(self, mode: str) -> None: + if not _assertion_supported(): + if mode == "plain": + warning_text = ( + "ASSERTIONS ARE NOT EXECUTED" + " and FAILING TESTS WILL PASS. Are you" + " using python -O?" + ) + else: + warning_text = ( + "assertions not in test modules or" + " plugins will be ignored" + " because assert statements are not executed " + "by the underlying Python interpreter " + "(are you using python -O?)\n" + ) + self.issue_config_time_warning( + PytestConfigWarning(warning_text), stacklevel=3, + ) + + def _warn_about_skipped_plugins(self) -> None: + for module_name, msg in self.pluginmanager.skipped_plugins: + self.issue_config_time_warning( + PytestConfigWarning("skipped plugin {!r}: {}".format(module_name, msg)), + stacklevel=2, + ) + -def _assertion_supported(): +def _assertion_supported() -> bool: try: assert False except AssertionError: return True else: - return False - - -def _warn_about_missing_assertion(mode): - if not _assertion_supported(): - if mode == "plain": - sys.stderr.write( - "WARNING: ASSERTIONS ARE NOT EXECUTED" - " and FAILING TESTS WILL PASS. Are you" - " using python -O?" - ) - else: - sys.stderr.write( - "WARNING: assertions not in test modules or" - " plugins will be ignored" - " because assert statements are not executed " - "by the underlying Python interpreter " - "(are you using python -O?)\n" - ) + return False # type: ignore[unreachable] -def setns(obj, dic): - import pytest - - for name, value in dic.items(): - if isinstance(value, dict): - mod = getattr(obj, name, None) - if mod is None: - modname = "pytest.%s" % name - mod = types.ModuleType(modname) - sys.modules[modname] = mod - mod.__all__ = [] - setattr(obj, name, mod) - obj.__all__.append(name) - setns(mod, value) - else: - setattr(obj, name, value) - obj.__all__.append(name) - # if obj != pytest: - # pytest.__all__.append(name) - setattr(pytest, name, value) - - -def create_terminal_writer(config, *args, **kwargs): +def create_terminal_writer( + config: Config, file: Optional[TextIO] = None +) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options - in the config object. Every code which requires a TerminalWriter object - and has access to a config object should use this function. + in the config object. + + Every code which requires a TerminalWriter object and has access to a + config object should use this function. """ - tw = py.io.TerminalWriter(*args, **kwargs) + tw = TerminalWriter(file=file) + if config.option.color == "yes": tw.hasmarkup = True - if config.option.color == "no": + elif config.option.color == "no": tw.hasmarkup = False + + if config.option.code_highlight == "yes": + tw.code_highlight = True + elif config.option.code_highlight == "no": + tw.code_highlight = False + return tw -def _strtobool(val): - """Convert a string representation of truth to true (1) or false (0). +def _strtobool(val: str) -> bool: + """Convert a string representation of truth to True or False. True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 'val' is anything else. - .. note:: copied from distutils.util + .. note:: Copied from distutils.util. """ val = val.lower() if val in ("y", "yes", "t", "true", "on", "1"): - return 1 + return True elif val in ("n", "no", "f", "false", "off", "0"): - return 0 + return False + else: + raise ValueError("invalid truth value {!r}".format(val)) + + +@lru_cache(maxsize=50) +def parse_warning_filter( + arg: str, *, escape: bool +) -> "Tuple[str, str, Type[Warning], str, int]": + """Parse a warnings filter string. + + This is copied from warnings._setoption, but does not apply the filter, + only parses it, and makes the escaping optional. + """ + parts = arg.split(":") + if len(parts) > 5: + raise warnings._OptionError("too many fields (max 5): {!r}".format(arg)) + while len(parts) < 5: + parts.append("") + action_, message, category_, module, lineno_ = [s.strip() for s in parts] + action = warnings._getaction(action_) # type: str # type: ignore[attr-defined] + category = warnings._getcategory( + category_ + ) # type: Type[Warning] # type: ignore[attr-defined] + if message and escape: + message = re.escape(message) + if module and escape: + module = re.escape(module) + r"\Z" + if lineno_: + try: + lineno = int(lineno_) + if lineno < 0: + raise ValueError + except (ValueError, OverflowError) as e: + raise warnings._OptionError("invalid lineno {!r}".format(lineno_)) from e else: - raise ValueError("invalid truth value %r" % (val,)) + lineno = 0 + return action, message, category, module, lineno + + +def apply_warning_filters( + config_filters: Iterable[str], cmdline_filters: Iterable[str] +) -> None: + """Applies pytest-configured filters to the warnings module""" + # Filters should have this precedence: cmdline options, config. + # Filters should be applied in the inverse order of precedence. + for arg in config_filters: + warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) + + for arg in cmdline_filters: + warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py index 37fb772db99..636021df455 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/argparsing.py @@ -1,48 +1,72 @@ -# -*- coding: utf-8 -*- import argparse +import sys import warnings +from gettext import gettext +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Union import py -import six +import _pytest._io +from _pytest.compat import final +from _pytest.compat import TYPE_CHECKING from _pytest.config.exceptions import UsageError +if TYPE_CHECKING: + from typing import NoReturn + from typing_extensions import Literal + FILE_OR_DIR = "file_or_dir" -class Parser(object): - """ Parser for command line arguments and ini-file values. +@final +class Parser: + """Parser for command line arguments and ini-file values. - :ivar extra_info: dict of generic param -> value to display in case + :ivar extra_info: Dict of generic param -> value to display in case there's an error processing the command line arguments. """ - prog = None + prog = None # type: Optional[str] - def __init__(self, usage=None, processopt=None): + def __init__( + self, + usage: Optional[str] = None, + processopt: Optional[Callable[["Argument"], None]] = None, + ) -> None: self._anonymous = OptionGroup("custom options", parser=self) - self._groups = [] + self._groups = [] # type: List[OptionGroup] self._processopt = processopt self._usage = usage - self._inidict = {} - self._ininames = [] - self.extra_info = {} + self._inidict = {} # type: Dict[str, Tuple[str, Optional[str], Any]] + self._ininames = [] # type: List[str] + self.extra_info = {} # type: Dict[str, Any] - def processoption(self, option): + def processoption(self, option: "Argument") -> None: if self._processopt: if option.dest: self._processopt(option) - def getgroup(self, name, description="", after=None): - """ get (or create) a named option Group. + def getgroup( + self, name: str, description: str = "", after: Optional[str] = None + ) -> "OptionGroup": + """Get (or create) a named option Group. - :name: name of the option group. - :description: long description for --help output. - :after: name of other group, used for ordering --help output. + :name: Name of the option group. + :description: Long description for --help output. + :after: Name of another group, used for ordering --help output. The returned group object has an ``addoption`` method with the same signature as :py:func:`parser.addoption - <_pytest.config.Parser.addoption>` but will be shown in the + <_pytest.config.argparsing.Parser.addoption>` but will be shown in the respective group in the output of ``pytest. --help``. """ for group in self._groups: @@ -56,31 +80,34 @@ class Parser(object): self._groups.insert(i + 1, group) return group - def addoption(self, *opts, **attrs): - """ register a command line option. + def addoption(self, *opts: str, **attrs: Any) -> None: + """Register a command line option. - :opts: option names, can be short or long options. - :attrs: same attributes which the ``add_option()`` function of the - `argparse library - <http://docs.python.org/2/library/argparse.html>`_ + :opts: Option names, can be short or long options. + :attrs: Same attributes which the ``add_argument()`` function of the + `argparse library <https://docs.python.org/library/argparse.html>`_ accepts. - After command line parsing options are available on the pytest config + After command line parsing, options are available on the pytest config object via ``config.option.NAME`` where ``NAME`` is usually set by passing a ``dest`` attribute, for example ``addoption("--long", dest="NAME", ...)``. """ self._anonymous.addoption(*opts, **attrs) - def parse(self, args, namespace=None): + def parse( + self, + args: Sequence[Union[str, py.path.local]], + namespace: Optional[argparse.Namespace] = None, + ) -> argparse.Namespace: from _pytest._argcomplete import try_argcomplete self.optparser = self._getparser() try_argcomplete(self.optparser) - args = [str(x) if isinstance(x, py.path.local) else x for x in args] - return self.optparser.parse_args(args, namespace=namespace) + strargs = [str(x) if isinstance(x, py.path.local) else x for x in args] + return self.optparser.parse_args(strargs, namespace=namespace) - def _getparser(self): + def _getparser(self) -> "MyOptionParser": from _pytest._argcomplete import filescompleter optparser = MyOptionParser(self, self.extra_info, prog=self.prog) @@ -93,37 +120,55 @@ class Parser(object): n = option.names() a = option.attrs() arggroup.add_argument(*n, **a) + file_or_dir_arg = optparser.add_argument(FILE_OR_DIR, nargs="*") # bash like autocompletion for dirs (appending '/') - optparser.add_argument(FILE_OR_DIR, nargs="*").completer = filescompleter + # Type ignored because typeshed doesn't know about argcomplete. + file_or_dir_arg.completer = filescompleter # type: ignore return optparser - def parse_setoption(self, args, option, namespace=None): + def parse_setoption( + self, + args: Sequence[Union[str, py.path.local]], + option: argparse.Namespace, + namespace: Optional[argparse.Namespace] = None, + ) -> List[str]: parsedoption = self.parse(args, namespace=namespace) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) - return getattr(parsedoption, FILE_OR_DIR) - - def parse_known_args(self, args, namespace=None): - """parses and returns a namespace object with known arguments at this - point. - """ + return cast(List[str], getattr(parsedoption, FILE_OR_DIR)) + + def parse_known_args( + self, + args: Sequence[Union[str, py.path.local]], + namespace: Optional[argparse.Namespace] = None, + ) -> argparse.Namespace: + """Parse and return a namespace object with known arguments at this point.""" return self.parse_known_and_unknown_args(args, namespace=namespace)[0] - def parse_known_and_unknown_args(self, args, namespace=None): - """parses and returns a namespace object with known arguments, and - the remaining arguments unknown at this point. - """ + def parse_known_and_unknown_args( + self, + args: Sequence[Union[str, py.path.local]], + namespace: Optional[argparse.Namespace] = None, + ) -> Tuple[argparse.Namespace, List[str]]: + """Parse and return a namespace object with known arguments, and + the remaining arguments unknown at this point.""" optparser = self._getparser() - args = [str(x) if isinstance(x, py.path.local) else x for x in args] - return optparser.parse_known_args(args, namespace=namespace) - - def addini(self, name, help, type=None, default=None): - """ register an ini-file option. - - :name: name of the ini-variable - :type: type of the variable, can be ``pathlist``, ``args``, ``linelist`` + strargs = [str(x) if isinstance(x, py.path.local) else x for x in args] + return optparser.parse_known_args(strargs, namespace=namespace) + + def addini( + self, + name: str, + help: str, + type: Optional["Literal['pathlist', 'args', 'linelist', 'bool']"] = None, + default=None, + ) -> None: + """Register an ini-file option. + + :name: Name of the ini-variable. + :type: Type of the variable, can be ``pathlist``, ``args``, ``linelist`` or ``bool``. - :default: default value if no ini-file option exists but is queried. + :default: Default value if no ini-file option exists but is queried. The value of ini-variables can be retrieved via a call to :py:func:`config.getini(name) <_pytest.config.Config.getini>`. @@ -134,38 +179,36 @@ class Parser(object): class ArgumentError(Exception): - """ - Raised if an Argument instance is created with invalid or - inconsistent arguments. - """ + """Raised if an Argument instance is created with invalid or + inconsistent arguments.""" - def __init__(self, msg, option): + def __init__(self, msg: str, option: Union["Argument", str]) -> None: self.msg = msg self.option_id = str(option) - def __str__(self): + def __str__(self) -> str: if self.option_id: - return "option %s: %s" % (self.option_id, self.msg) + return "option {}: {}".format(self.option_id, self.msg) else: return self.msg -class Argument(object): - """class that mimics the necessary behaviour of optparse.Option +class Argument: + """Class that mimics the necessary behaviour of optparse.Option. + + It's currently a least effort implementation and ignoring choices + and integer prefixes. - it's currently a least effort implementation - and ignoring choices and integer prefixes https://docs.python.org/3/library/optparse.html#optparse-standard-option-types """ _typ_map = {"int": int, "string": str, "float": float, "complex": complex} - def __init__(self, *names, **attrs): - """store parms in private vars for use in add_argument""" + def __init__(self, *names: str, **attrs: Any) -> None: + """Store parms in private vars for use in add_argument.""" self._attrs = attrs - self._short_opts = [] - self._long_opts = [] - self.dest = attrs.get("dest") + self._short_opts = [] # type: List[str] + self._long_opts = [] # type: List[str] if "%default" in (attrs.get("help") or ""): warnings.warn( 'pytest now uses argparse. "%default" should be' @@ -178,8 +221,8 @@ class Argument(object): except KeyError: pass else: - # this might raise a keyerror as well, don't want to catch that - if isinstance(typ, six.string_types): + # This might raise a keyerror as well, don't want to catch that. + if isinstance(typ, str): if typ == "choice": warnings.warn( "`type` argument to addoption() is the string %r." @@ -201,33 +244,35 @@ class Argument(object): stacklevel=4, ) attrs["type"] = Argument._typ_map[typ] - # used in test_parseopt -> test_parse_defaultgetter + # Used in test_parseopt -> test_parse_defaultgetter. self.type = attrs["type"] else: self.type = typ try: - # attribute existence is tested in Config._processopt + # Attribute existence is tested in Config._processopt. self.default = attrs["default"] except KeyError: pass self._set_opt_strings(names) - if not self.dest: - if self._long_opts: - self.dest = self._long_opts[0][2:].replace("-", "_") - else: - try: - self.dest = self._short_opts[0][1:] - except IndexError: - raise ArgumentError("need a long or short option", self) + dest = attrs.get("dest") # type: Optional[str] + if dest: + self.dest = dest + elif self._long_opts: + self.dest = self._long_opts[0][2:].replace("-", "_") + else: + try: + self.dest = self._short_opts[0][1:] + except IndexError as e: + self.dest = "???" # Needed for the error repr. + raise ArgumentError("need a long or short option", self) from e - def names(self): + def names(self) -> List[str]: return self._short_opts + self._long_opts - def attrs(self): - # update any attributes set by processopt + def attrs(self) -> Mapping[str, Any]: + # Update any attributes set by processopt. attrs = "default dest help".split() - if self.dest: - attrs.append(self.dest) + attrs.append(self.dest) for attr in attrs: try: self._attrs[attr] = getattr(self, attr) @@ -240,10 +285,11 @@ class Argument(object): self._attrs["help"] = a return self._attrs - def _set_opt_strings(self, opts): - """directly from optparse + def _set_opt_strings(self, opts: Sequence[str]) -> None: + """Directly from optparse. - might not be necessary as this is passed to argparse later on""" + Might not be necessary as this is passed to argparse later on. + """ for opt in opts: if len(opt) < 2: raise ArgumentError( @@ -268,8 +314,8 @@ class Argument(object): ) self._long_opts.append(opt) - def __repr__(self): - args = [] + def __repr__(self) -> str: + args = [] # type: List[str] if self._short_opts: args += ["_short_opts: " + repr(self._short_opts)] if self._long_opts: @@ -282,20 +328,22 @@ class Argument(object): return "Argument({})".format(", ".join(args)) -class OptionGroup(object): - def __init__(self, name, description="", parser=None): +class OptionGroup: + def __init__( + self, name: str, description: str = "", parser: Optional[Parser] = None + ) -> None: self.name = name self.description = description - self.options = [] + self.options = [] # type: List[Argument] self.parser = parser - def addoption(self, *optnames, **attrs): - """ add an option to this group. + def addoption(self, *optnames: str, **attrs: Any) -> None: + """Add an option to this group. - if a shortened version of a long option is specified it will + If a shortened version of a long option is specified, it will be suppressed in the help. addoption('--twowords', '--two-words') results in help showing '--two-words' only, but --twowords gets - accepted **and** the automatic destination is in args.twowords + accepted **and** the automatic destination is in args.twowords. """ conflict = set(optnames).intersection( name for opt in self.options for name in opt.names() @@ -305,11 +353,11 @@ class OptionGroup(object): option = Argument(*optnames, **attrs) self._addoption_instance(option, shortupper=False) - def _addoption(self, *optnames, **attrs): + def _addoption(self, *optnames: str, **attrs: Any) -> None: option = Argument(*optnames, **attrs) self._addoption_instance(option, shortupper=True) - def _addoption_instance(self, option, shortupper=False): + def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: if not shortupper: for opt in option._short_opts: if opt[0] == "-" and opt[1].islower(): @@ -320,9 +368,12 @@ class OptionGroup(object): class MyOptionParser(argparse.ArgumentParser): - def __init__(self, parser, extra_info=None, prog=None): - if not extra_info: - extra_info = {} + def __init__( + self, + parser: Parser, + extra_info: Optional[Dict[str, Any]] = None, + prog: Optional[str] = None, + ) -> None: self._parser = parser argparse.ArgumentParser.__init__( self, @@ -330,75 +381,122 @@ class MyOptionParser(argparse.ArgumentParser): usage=parser._usage, add_help=False, formatter_class=DropShorterLongHelpFormatter, + allow_abbrev=False, ) # extra_info is a dict of (param -> value) to display if there's - # an usage error to provide more contextual information to the user - self.extra_info = extra_info + # an usage error to provide more contextual information to the user. + self.extra_info = extra_info if extra_info else {} - def error(self, message): + def error(self, message: str) -> "NoReturn": """Transform argparse error message into UsageError.""" - msg = "%s: error: %s" % (self.prog, message) + msg = "{}: error: {}".format(self.prog, message) if hasattr(self._parser, "_config_source_hint"): - msg = "%s (%s)" % (msg, self._parser._config_source_hint) + # Type ignored because the attribute is set dynamically. + msg = "{} ({})".format(msg, self._parser._config_source_hint) # type: ignore raise UsageError(self.format_usage() + msg) - def parse_args(self, args=None, namespace=None): - """allow splitting of positional arguments""" - args, argv = self.parse_known_args(args, namespace) - if argv: - for arg in argv: + # Type ignored because typeshed has a very complex type in the superclass. + def parse_args( # type: ignore + self, + args: Optional[Sequence[str]] = None, + namespace: Optional[argparse.Namespace] = None, + ) -> argparse.Namespace: + """Allow splitting of positional arguments.""" + parsed, unrecognized = self.parse_known_args(args, namespace) + if unrecognized: + for arg in unrecognized: if arg and arg[0] == "-": - lines = ["unrecognized arguments: %s" % (" ".join(argv))] + lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))] for k, v in sorted(self.extra_info.items()): - lines.append(" %s: %s" % (k, v)) + lines.append(" {}: {}".format(k, v)) self.error("\n".join(lines)) - getattr(args, FILE_OR_DIR).extend(argv) - return args + getattr(parsed, FILE_OR_DIR).extend(unrecognized) + return parsed + + if sys.version_info[:2] < (3, 9): # pragma: no cover + # Backport of https://github.com/python/cpython/pull/14316 so we can + # disable long --argument abbreviations without breaking short flags. + def _parse_optional( + self, arg_string: str + ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]: + if not arg_string: + return None + if not arg_string[0] in self.prefix_chars: + return None + if arg_string in self._option_string_actions: + action = self._option_string_actions[arg_string] + return action, arg_string, None + if len(arg_string) == 1: + return None + if "=" in arg_string: + option_string, explicit_arg = arg_string.split("=", 1) + if option_string in self._option_string_actions: + action = self._option_string_actions[option_string] + return action, option_string, explicit_arg + if self.allow_abbrev or not arg_string.startswith("--"): + option_tuples = self._get_option_tuples(arg_string) + if len(option_tuples) > 1: + msg = gettext( + "ambiguous option: %(option)s could match %(matches)s" + ) + options = ", ".join(option for _, option, _ in option_tuples) + self.error(msg % {"option": arg_string, "matches": options}) + elif len(option_tuples) == 1: + (option_tuple,) = option_tuples + return option_tuple + if self._negative_number_matcher.match(arg_string): + if not self._has_negative_number_optionals: + return None + if " " in arg_string: + return None + return None, arg_string, None class DropShorterLongHelpFormatter(argparse.HelpFormatter): - """shorten help for long options that differ only in extra hyphens + """Shorten help for long options that differ only in extra hyphens. - - collapse **long** options that are the same except for extra hyphens - - special action attribute map_long_option allows surpressing additional - long options - - shortcut if there are only two options and one of them is a short one - - cache result on action object as this is called at least 2 times + - Collapse **long** options that are the same except for extra hyphens. + - Shortcut if there are only two options and one of them is a short one. + - Cache result on the action object as this is called at least 2 times. """ - def _format_action_invocation(self, action): + def __init__(self, *args: Any, **kwargs: Any) -> None: + # Use more accurate terminal width. + if "width" not in kwargs: + kwargs["width"] = _pytest._io.get_terminal_width() + super().__init__(*args, **kwargs) + + def _format_action_invocation(self, action: argparse.Action) -> str: orgstr = argparse.HelpFormatter._format_action_invocation(self, action) if orgstr and orgstr[0] != "-": # only optional arguments return orgstr - res = getattr(action, "_formatted_action_invocation", None) + res = getattr( + action, "_formatted_action_invocation", None + ) # type: Optional[str] if res: return res options = orgstr.split(", ") if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2): # a shortcut for '-h, --help' or '--abc', '-a' - action._formatted_action_invocation = orgstr + action._formatted_action_invocation = orgstr # type: ignore return orgstr return_list = [] - option_map = getattr(action, "map_long_option", {}) - if option_map is None: - option_map = {} - short_long = {} + short_long = {} # type: Dict[str, str] for option in options: if len(option) == 2 or option[2] == " ": continue if not option.startswith("--"): raise ArgumentError( - 'long optional argument without "--": [%s]' % (option), self + 'long optional argument without "--": [%s]' % (option), option ) xxoption = option[2:] - if xxoption.split()[0] not in option_map: - shortened = xxoption.replace("-", "") - if shortened not in short_long or len(short_long[shortened]) < len( - xxoption - ): - short_long[shortened] = xxoption + shortened = xxoption.replace("-", "") + if shortened not in short_long or len(short_long[shortened]) < len( + xxoption + ): + short_long[shortened] = xxoption # now short_long has been filled out to the longest with dashes # **and** we keep the right option ordering from add_argument for option in options: @@ -406,5 +504,18 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): return_list.append(option) if option[2:] == short_long.get(option.replace("-", "")): return_list.append(option.replace(" ", "=", 1)) - action._formatted_action_invocation = ", ".join(return_list) - return action._formatted_action_invocation + formatted_action_invocation = ", ".join(return_list) + action._formatted_action_invocation = formatted_action_invocation # type: ignore + return formatted_action_invocation + + def _split_lines(self, text, width): + """Wrap lines after splitting on original newlines. + + This allows to have explicit line breaks in the help text. + """ + import textwrap + + lines = [] + for line in text.splitlines(): + lines.extend(textwrap.wrap(line.strip(), width)) + return lines diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py index bf58fde5dbf..4f1320e758d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/exceptions.py @@ -1,10 +1,11 @@ -# -*- coding: utf-8 -*- +from _pytest.compat import final + + +@final class UsageError(Exception): - """ error in pytest usage or invocation""" + """Error in pytest usage or invocation.""" class PrintHelp(Exception): - """Raised when pytest should print it's help to skip the rest of the + """Raised when pytest should print its help to skip the rest of the argument parsing and validation.""" - - pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py index e6779b289bc..167b9e7a006 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/config/findpaths.py @@ -1,153 +1,211 @@ -# -*- coding: utf-8 -*- import os +from typing import Dict +from typing import Iterable +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Union -import py +import iniconfig from .exceptions import UsageError +from _pytest.compat import TYPE_CHECKING from _pytest.outcomes import fail +from _pytest.pathlib import absolutepath +from _pytest.pathlib import commonpath +from _pytest.pathlib import Path +if TYPE_CHECKING: + from . import Config -def exists(path, ignore=EnvironmentError): - try: - return path.check() - except ignore: - return False +def _parse_ini_config(path: Path) -> iniconfig.IniConfig: + """Parse the given generic '.ini' file using legacy IniConfig parser, returning + the parsed object. -def getcfg(args, config=None): + Raise UsageError if the file cannot be parsed. """ - Search the list of arguments for a valid ini-file for pytest, - and return a tuple of (rootdir, inifile, cfg-dict). + try: + return iniconfig.IniConfig(path) + except iniconfig.ParseError as exc: + raise UsageError(str(exc)) from exc + - note: config is optional and used only to issue warnings explicitly (#2891). +def load_config_dict_from_file( + filepath: Path, +) -> Optional[Dict[str, Union[str, List[str]]]]: + """Load pytest configuration from the given file path, if supported. + + Return None if the file does not contain valid pytest configuration. """ - from _pytest.deprecated import CFG_PYTEST_SECTION - inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"] + # Configuration from ini files are obtained from the [pytest] section, if present. + if filepath.suffix == ".ini": + iniconfig = _parse_ini_config(filepath) + + if "pytest" in iniconfig: + return dict(iniconfig["pytest"].items()) + else: + # "pytest.ini" files are always the source of configuration, even if empty. + if filepath.name == "pytest.ini": + return {} + + # '.cfg' files are considered if they contain a "[tool:pytest]" section. + elif filepath.suffix == ".cfg": + iniconfig = _parse_ini_config(filepath) + + if "tool:pytest" in iniconfig.sections: + return dict(iniconfig["tool:pytest"].items()) + elif "pytest" in iniconfig.sections: + # If a setup.cfg contains a "[pytest]" section, we raise a failure to indicate users that + # plain "[pytest]" sections in setup.cfg files is no longer supported (#3086). + fail(CFG_PYTEST_SECTION.format(filename="setup.cfg"), pytrace=False) + + # '.toml' files are considered if they contain a [tool.pytest.ini_options] table. + elif filepath.suffix == ".toml": + import toml + + config = toml.load(str(filepath)) + + result = config.get("tool", {}).get("pytest", {}).get("ini_options", None) + if result is not None: + # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc), + # however we need to convert all scalar values to str for compatibility with the rest + # of the configuration system, which expects strings only. + def make_scalar(v: object) -> Union[str, List[str]]: + return v if isinstance(v, list) else str(v) + + return {k: make_scalar(v) for k, v in result.items()} + + return None + + +def locate_config( + args: Iterable[Path], +) -> Tuple[ + Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]], +]: + """Search in the list of arguments for a valid ini-file for pytest, + and return a tuple of (rootdir, inifile, cfg-dict).""" + config_names = [ + "pytest.ini", + "pyproject.toml", + "tox.ini", + "setup.cfg", + ] args = [x for x in args if not str(x).startswith("-")] if not args: - args = [py.path.local()] + args = [Path.cwd()] for arg in args: - arg = py.path.local(arg) - for base in arg.parts(reverse=True): - for inibasename in inibasenames: - p = base.join(inibasename) - if exists(p): - try: - iniconfig = py.iniconfig.IniConfig(p) - except py.iniconfig.ParseError as exc: - raise UsageError(str(exc)) - - if ( - inibasename == "setup.cfg" - and "tool:pytest" in iniconfig.sections - ): - return base, p, iniconfig["tool:pytest"] - elif "pytest" in iniconfig.sections: - if inibasename == "setup.cfg" and config is not None: - - fail( - CFG_PYTEST_SECTION.format(filename=inibasename), - pytrace=False, - ) - return base, p, iniconfig["pytest"] - elif inibasename == "pytest.ini": - # allowed to be empty - return base, p, {} - return None, None, None - - -def get_common_ancestor(paths): - common_ancestor = None + argpath = absolutepath(arg) + for base in (argpath, *argpath.parents): + for config_name in config_names: + p = base / config_name + if p.is_file(): + ini_config = load_config_dict_from_file(p) + if ini_config is not None: + return base, p, ini_config + return None, None, {} + + +def get_common_ancestor(paths: Iterable[Path]) -> Path: + common_ancestor = None # type: Optional[Path] for path in paths: if not path.exists(): continue if common_ancestor is None: common_ancestor = path else: - if path.relto(common_ancestor) or path == common_ancestor: + if common_ancestor in path.parents or path == common_ancestor: continue - elif common_ancestor.relto(path): + elif path in common_ancestor.parents: common_ancestor = path else: - shared = path.common(common_ancestor) + shared = commonpath(path, common_ancestor) if shared is not None: common_ancestor = shared if common_ancestor is None: - common_ancestor = py.path.local() - elif common_ancestor.isfile(): - common_ancestor = common_ancestor.dirpath() + common_ancestor = Path.cwd() + elif common_ancestor.is_file(): + common_ancestor = common_ancestor.parent return common_ancestor -def get_dirs_from_args(args): - def is_option(x): - return str(x).startswith("-") +def get_dirs_from_args(args: Iterable[str]) -> List[Path]: + def is_option(x: str) -> bool: + return x.startswith("-") - def get_file_part_from_node_id(x): - return str(x).split("::")[0] + def get_file_part_from_node_id(x: str) -> str: + return x.split("::")[0] - def get_dir_from_path(path): - if path.isdir(): + def get_dir_from_path(path: Path) -> Path: + if path.is_dir(): return path - return py.path.local(path.dirname) + return path.parent + + def safe_exists(path: Path) -> bool: + # This can throw on paths that contain characters unrepresentable at the OS level, + # or with invalid syntax on Windows (https://bugs.python.org/issue35306) + try: + return path.exists() + except OSError: + return False # These look like paths but may not exist possible_paths = ( - py.path.local(get_file_part_from_node_id(arg)) + absolutepath(get_file_part_from_node_id(arg)) for arg in args if not is_option(arg) ) - return [get_dir_from_path(path) for path in possible_paths if path.exists()] + return [get_dir_from_path(path) for path in possible_paths if safe_exists(path)] + + +CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." -def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None): +def determine_setup( + inifile: Optional[str], + args: Sequence[str], + rootdir_cmd_arg: Optional[str] = None, + config: Optional["Config"] = None, +) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: + rootdir = None dirs = get_dirs_from_args(args) if inifile: - iniconfig = py.iniconfig.IniConfig(inifile) - is_cfg_file = str(inifile).endswith(".cfg") - sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"] - for section in sections: - try: - inicfg = iniconfig[section] - if is_cfg_file and section == "pytest" and config is not None: - from _pytest.deprecated import CFG_PYTEST_SECTION - - fail( - CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False - ) - break - except KeyError: - inicfg = None + inipath_ = absolutepath(inifile) + inipath = inipath_ # type: Optional[Path] + inicfg = load_config_dict_from_file(inipath_) or {} if rootdir_cmd_arg is None: rootdir = get_common_ancestor(dirs) else: ancestor = get_common_ancestor(dirs) - rootdir, inifile, inicfg = getcfg([ancestor], config=config) + rootdir, inipath, inicfg = locate_config([ancestor]) if rootdir is None and rootdir_cmd_arg is None: - for possible_rootdir in ancestor.parts(reverse=True): - if possible_rootdir.join("setup.py").exists(): + for possible_rootdir in (ancestor, *ancestor.parents): + if (possible_rootdir / "setup.py").is_file(): rootdir = possible_rootdir break else: if dirs != [ancestor]: - rootdir, inifile, inicfg = getcfg(dirs, config=config) + rootdir, inipath, inicfg = locate_config(dirs) if rootdir is None: if config is not None: - cwd = config.invocation_dir + cwd = config.invocation_params.dir else: - cwd = py.path.local() + cwd = Path.cwd() rootdir = get_common_ancestor([cwd, ancestor]) is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" if is_fs_root: rootdir = ancestor if rootdir_cmd_arg: - rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg)) - if not rootdir.isdir(): + rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) + if not rootdir.is_dir(): raise UsageError( "Directory '{}' not found. Check your '--rootdir' option.".format( rootdir ) ) - return rootdir, inifile, inicfg or {} + assert rootdir is not None + return rootdir, inipath, inicfg or {} diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py index 99d35a5ab77..6f641fb2d96 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/debugging.py @@ -1,31 +1,47 @@ -# -*- coding: utf-8 -*- -""" interactive debugging with PDB, the Python Debugger. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Interactive debugging with PDB, the Python Debugger.""" import argparse -import pdb +import functools import sys -from doctest import UnexpectedException +import types +from typing import Any +from typing import Callable +from typing import Generator +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union from _pytest import outcomes +from _pytest._code import ExceptionInfo +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config import ConftestImportFailure from _pytest.config import hookimpl +from _pytest.config import PytestPluginManager +from _pytest.config.argparsing import Parser from _pytest.config.exceptions import UsageError +from _pytest.nodes import Node +from _pytest.reports import BaseReport + +if TYPE_CHECKING: + from typing import Type + from _pytest.capture import CaptureManager + from _pytest.runner import CallInfo -def _validate_usepdb_cls(value): + +def _validate_usepdb_cls(value: str) -> Tuple[str, str]: """Validate syntax of --pdbcls option.""" try: modname, classname = value.split(":") - except ValueError: + except ValueError as e: raise argparse.ArgumentTypeError( "{!r} is not in the format 'modname:classname'".format(value) - ) + ) from e return (modname, classname) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group._addoption( "--pdb", @@ -49,7 +65,9 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: + import pdb + if config.getvalue("trace"): config.pluginmanager.register(PdbTrace(), "pdbtrace") if config.getvalue("usepdb"): @@ -64,7 +82,7 @@ def pytest_configure(config): # NOTE: not using pytest_unconfigure, since it might get called although # pytest_configure was not (if another plugin raises UsageError). - def fin(): + def fin() -> None: ( pdb.set_trace, pytestPDB._pluginmanager, @@ -74,24 +92,28 @@ def pytest_configure(config): config._cleanup.append(fin) -class pytestPDB(object): - """ Pseudo PDB that defers to the real pdb. """ +class pytestPDB: + """Pseudo PDB that defers to the real pdb.""" - _pluginmanager = None - _config = None - _saved = [] + _pluginmanager = None # type: Optional[PytestPluginManager] + _config = None # type: Config + _saved = ( + [] + ) # type: List[Tuple[Callable[..., None], Optional[PytestPluginManager], Config]] _recursive_debug = 0 - _wrapped_pdb_cls = None + _wrapped_pdb_cls = None # type: Optional[Tuple[Type[Any], Type[Any]]] @classmethod - def _is_capturing(cls, capman): + def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]: if capman: return capman.is_capturing() return False @classmethod - def _import_pdb_cls(cls, capman): + def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): if not cls._config: + import pdb + # Happens when using pytest.set_trace outside of a test. return pdb.Pdb @@ -116,8 +138,10 @@ class pytestPDB(object): value = ":".join((modname, classname)) raise UsageError( "--pdbcls: could not import {!r}: {}".format(value, exc) - ) + ) from exc else: + import pdb + pdb_cls = pdb.Pdb wrapped_cls = cls._get_pdb_wrapper_class(pdb_cls, capman) @@ -125,21 +149,23 @@ class pytestPDB(object): return wrapped_cls @classmethod - def _get_pdb_wrapper_class(cls, pdb_cls, capman): + def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]): import _pytest.config - class PytestPdbWrapper(pdb_cls, object): + # Type ignored because mypy doesn't support "dynamic" + # inheritance like this. + class PytestPdbWrapper(pdb_cls): # type: ignore[valid-type,misc] _pytest_capman = capman _continued = False def do_debug(self, arg): cls._recursive_debug += 1 - ret = super(PytestPdbWrapper, self).do_debug(arg) + ret = super().do_debug(arg) cls._recursive_debug -= 1 return ret def do_continue(self, arg): - ret = super(PytestPdbWrapper, self).do_continue(arg) + ret = super().do_continue(arg) if cls._recursive_debug == 0: tw = _pytest.config.create_terminal_writer(cls._config) tw.line() @@ -155,9 +181,11 @@ class pytestPDB(object): "PDB continue (IO-capturing resumed for %s)" % capturing, ) + assert capman is not None capman.resume() else: tw.sep(">", "PDB continue") + assert cls._pluginmanager is not None cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config, pdb=self) self._continued = True return ret @@ -171,7 +199,7 @@ class pytestPDB(object): could be handled, but this would require to wrap the whole pytest run, and adjust the report etc. """ - ret = super(PytestPdbWrapper, self).do_quit(arg) + ret = super().do_quit(arg) if cls._recursive_debug == 0: outcomes.exit("Quitting debugger") @@ -187,7 +215,7 @@ class pytestPDB(object): Needed after do_continue resumed, and entering another breakpoint again. """ - ret = super(PytestPdbWrapper, self).setup(f, tb) + ret = super().setup(f, tb) if not ret and self._continued: # pdb.setup() returns True if the command wants to exit # from the interaction: do not suspend capturing then. @@ -196,7 +224,7 @@ class pytestPDB(object): return ret def get_stack(self, f, t): - stack, i = super(PytestPdbWrapper, self).get_stack(f, t) + stack, i = super().get_stack(f, t) if f is None: # Find last non-hidden frame. i = max(0, len(stack) - 1) @@ -208,13 +236,13 @@ class pytestPDB(object): @classmethod def _init_pdb(cls, method, *args, **kwargs): - """ Initialize PDB debugging, dropping any IO capturing. """ + """Initialize PDB debugging, dropping any IO capturing.""" import _pytest.config - if cls._pluginmanager is not None: - capman = cls._pluginmanager.getplugin("capturemanager") + if cls._pluginmanager is None: + capman = None # type: Optional[CaptureManager] else: - capman = None + capman = cls._pluginmanager.getplugin("capturemanager") if capman: capman.suspend(in_=True) @@ -230,7 +258,7 @@ class pytestPDB(object): else: capturing = cls._is_capturing(capman) if capturing == "global": - tw.sep(">", "PDB %s (IO-capturing turned off)" % (method,)) + tw.sep(">", "PDB {} (IO-capturing turned off)".format(method)) elif capturing: tw.sep( ">", @@ -238,7 +266,7 @@ class pytestPDB(object): % (method, capturing), ) else: - tw.sep(">", "PDB %s" % (method,)) + tw.sep(">", "PDB {}".format(method)) _pdb = cls._import_pdb_cls(capman)(**kwargs) @@ -247,48 +275,67 @@ class pytestPDB(object): return _pdb @classmethod - def set_trace(cls, *args, **kwargs): + def set_trace(cls, *args, **kwargs) -> None: """Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing.""" frame = sys._getframe().f_back _pdb = cls._init_pdb("set_trace", *args, **kwargs) _pdb.set_trace(frame) -class PdbInvoke(object): - def pytest_exception_interact(self, node, call, report): +class PdbInvoke: + def pytest_exception_interact( + self, node: Node, call: "CallInfo[Any]", report: BaseReport + ) -> None: capman = node.config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture(in_=True) out, err = capman.read_global_capture() sys.stdout.write(out) sys.stdout.write(err) + assert call.excinfo is not None _enter_pdb(node, call.excinfo, report) - def pytest_internalerror(self, excrepr, excinfo): + def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: tb = _postmortem_traceback(excinfo) post_mortem(tb) -class PdbTrace(object): +class PdbTrace: @hookimpl(hookwrapper=True) - def pytest_pyfunc_call(self, pyfuncitem): - _test_pytest_function(pyfuncitem) + def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, None, None]: + wrap_pytest_function_for_tracing(pyfuncitem) yield -def _test_pytest_function(pyfuncitem): +def wrap_pytest_function_for_tracing(pyfuncitem): + """Change the Python function object of the given Function item by a + wrapper which actually enters pdb before calling the python function + itself, effectively leaving the user in the pdb prompt in the first + statement of the function.""" _pdb = pytestPDB._init_pdb("runcall") testfunction = pyfuncitem.obj - pyfuncitem.obj = _pdb.runcall - if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch - raise ValueError("--trace can't be used with a fixture named func!") - pyfuncitem.funcargs["func"] = testfunction - new_list = list(pyfuncitem._fixtureinfo.argnames) - new_list.append("func") - pyfuncitem._fixtureinfo.argnames = tuple(new_list) + # we can't just return `partial(pdb.runcall, testfunction)` because (on + # python < 3.7.4) runcall's first param is `func`, which means we'd get + # an exception if one of the kwargs to testfunction was called `func`. + @functools.wraps(testfunction) + def wrapper(*args, **kwargs): + func = functools.partial(testfunction, *args, **kwargs) + _pdb.runcall(func) + + pyfuncitem.obj = wrapper + + +def maybe_wrap_pytest_function_for_tracing(pyfuncitem): + """Wrap the given pytestfunct item for tracing support if --trace was given in + the command line.""" + if pyfuncitem.config.getvalue("trace"): + wrap_pytest_function_for_tracing(pyfuncitem) -def _enter_pdb(node, excinfo, rep): + +def _enter_pdb( + node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport +) -> BaseReport: # XXX we re-use the TerminalReporter's terminalwriter # because this seems to avoid some encoding related troubles # for not completely clear reasons. @@ -312,21 +359,28 @@ def _enter_pdb(node, excinfo, rep): rep.toterminal(tw) tw.sep(">", "entering PDB") tb = _postmortem_traceback(excinfo) - rep._pdbshown = True + rep._pdbshown = True # type: ignore[attr-defined] post_mortem(tb) return rep -def _postmortem_traceback(excinfo): +def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType: + from doctest import UnexpectedException + if isinstance(excinfo.value, UnexpectedException): # A doctest.UnexpectedException is not useful for post_mortem. # Use the underlying exception instead: return excinfo.value.exc_info[2] + elif isinstance(excinfo.value, ConftestImportFailure): + # A config.ConftestImportFailure is not useful for post_mortem. + # Use the underlying exception instead: + return excinfo.value.excinfo[2] else: + assert excinfo._excinfo is not None return excinfo._excinfo[2] -def post_mortem(t): +def post_mortem(t: types.TracebackType) -> None: p = pytestPDB._init_pdb("post_mortem") p.reset() p.interaction(None, t) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py index 12394aca3f5..ecdb60d37f5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/deprecated.py @@ -1,96 +1,52 @@ -# -*- coding: utf-8 -*- -""" -This module contains deprecation messages and bits of code used elsewhere in the codebase -that is planned to be removed in the next pytest release. +"""Deprecation messages and bits of code used elsewhere in the codebase that +is planned to be removed in the next pytest release. Keeping it in a central location makes it easy to track what is deprecated and should be removed when the time comes. -All constants defined in this module should be either PytestWarning instances or UnformattedWarning +All constants defined in this module should be either instances of +:class:`PytestWarning`, or :class:`UnformattedWarning` in case of warnings which need to format their messages. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warning_types import RemovedInPytest4Warning from _pytest.warning_types import UnformattedWarning -YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored" - - -FIXTURE_FUNCTION_CALL = ( - 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n' - "but are created automatically when test functions request them as parameters.\n" - "See https://docs.pytest.org/en/latest/fixture.html for more information about fixtures, and\n" - "https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly about how to update your code." -) - -FIXTURE_NAMED_REQUEST = PytestDeprecationWarning( - "'request' is a reserved name for fixtures and will raise an error in future versions" -) - -CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." - -GETFUNCARGVALUE = RemovedInPytest4Warning( - "getfuncargvalue is deprecated, use getfixturevalue" -) +# set of plugins which have been integrated into the core; we use this list to ignore +# them during registration to avoid conflicts +DEPRECATED_EXTERNAL_PLUGINS = { + "pytest_catchlog", + "pytest_capturelog", + "pytest_faulthandler", +} -RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning( - "The 'message' parameter is deprecated.\n" - "(did you mean to use `match='some regex'` to check the exception message?)\n" - "Please see:\n" - " https://docs.pytest.org/en/4.6-maintenance/deprecations.html#message-parameter-of-pytest-raises" -) -RESULT_LOG = PytestDeprecationWarning( - "--result-log is deprecated and scheduled for removal in pytest 5.0.\n" - "See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information." -) - -RAISES_EXEC = PytestDeprecationWarning( - "raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n" - "See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec" -) -WARNS_EXEC = PytestDeprecationWarning( - "warns(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly.\n\n" - "See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec" +FILLFUNCARGS = PytestDeprecationWarning( + "The `_fillfuncargs` function is deprecated, use " + "function._request._fillfixtures() instead if you cannot avoid reaching into internals." ) -PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = ( - "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported " - "because it affects the entire directory tree in a non-explicit way.\n" - " {}\n" - "Please move it to a top level conftest file at the rootdir:\n" - " {}\n" - "For more information, visit:\n" - " https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" +PYTEST_COLLECT_MODULE = UnformattedWarning( + PytestDeprecationWarning, + "pytest.collect.{name} was moved to pytest.{name}\n" + "Please update to the new name.", ) -PYTEST_CONFIG_GLOBAL = PytestDeprecationWarning( - "the `pytest.config` global is deprecated. Please use `request.config` " - "or `pytest_configure` (if you're a pytest plugin) instead." -) -PYTEST_ENSURETEMP = RemovedInPytest4Warning( - "pytest/tmpdir_factory.ensuretemp is deprecated, \n" - "please use the tmp_path fixture or tmp_path_factory.mktemp" +MINUS_K_DASH = PytestDeprecationWarning( + "The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead." ) -PYTEST_LOGWARNING = PytestDeprecationWarning( - "pytest_logwarning is deprecated, no longer being called, and will be removed soon\n" - "please use pytest_warning_captured instead" +MINUS_K_COLON = PytestDeprecationWarning( + "The `-k 'expr:'` syntax to -k is deprecated.\n" + "Please open an issue if you use this and want a replacement." ) -PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning( - PytestDeprecationWarning, - "pytest.warns() got unexpected keyword arguments: {args!r}.\n" - "This will be an error in future versions.", +WARNING_CAPTURED_HOOK = PytestDeprecationWarning( + "The pytest_warning_captured is deprecated and will be removed in a future release.\n" + "Please use pytest_warning_recorded instead." ) -PYTEST_PARAM_UNKNOWN_KWARGS = UnformattedWarning( - PytestDeprecationWarning, - "pytest.param() got unexpected keyword arguments: {args!r}.\n" - "This will be an error in future versions.", +FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestDeprecationWarning( + "The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; " + "use self.session.gethookproxy() and self.session.isinitpath() instead. " ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py index 659d24aeebc..c744bb369ea 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/doctest.py @@ -1,25 +1,47 @@ -# -*- coding: utf-8 -*- -""" discover and run doctests in modules and test files.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Discover and run doctests in modules and test files.""" +import bdb import inspect import platform import sys import traceback +import types import warnings from contextlib import contextmanager +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Pattern +from typing import Sequence +from typing import Tuple +from typing import Union + +import py.path import pytest +from _pytest import outcomes from _pytest._code.code import ExceptionInfo from _pytest._code.code import ReprFileLocation from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter from _pytest.compat import safe_getattr +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest -from _pytest.outcomes import Skipped +from _pytest.nodes import Collector +from _pytest.outcomes import OutcomeException +from _pytest.pathlib import import_path +from _pytest.python_api import approx from _pytest.warning_types import PytestWarning +if TYPE_CHECKING: + import doctest + from typing import Type + DOCTEST_REPORT_CHOICE_NONE = "none" DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" @@ -36,9 +58,11 @@ DOCTEST_REPORT_CHOICES = ( # Lazy definition of runner class RUNNER_CLASS = None +# Lazy definition of output checker class +CHECKER_CLASS = None # type: Optional[Type[doctest.OutputChecker]] -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addini( "doctest_optionflags", "option flags for doctests", @@ -88,23 +112,34 @@ def pytest_addoption(parser): ) -def pytest_collect_file(path, parent): +def pytest_unconfigure() -> None: + global RUNNER_CLASS + + RUNNER_CLASS = None + + +def pytest_collect_file( + path: py.path.local, parent: Collector, +) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: config = parent.config if path.ext == ".py": - if config.option.doctestmodules and not _is_setup_py(config, path, parent): - return DoctestModule(path, parent) + if config.option.doctestmodules and not _is_setup_py(path): + mod = DoctestModule.from_parent(parent, fspath=path) # type: DoctestModule + return mod elif _is_doctest(config, path, parent): - return DoctestTextfile(path, parent) + txt = DoctestTextfile.from_parent(parent, fspath=path) # type: DoctestTextfile + return txt + return None -def _is_setup_py(config, path, parent): +def _is_setup_py(path: py.path.local) -> bool: if path.basename != "setup.py": return False - contents = path.read() - return "setuptools" in contents or "distutils" in contents + contents = path.read_binary() + return b"setuptools" in contents or b"distutils" in contents -def _is_doctest(config, path, parent): +def _is_doctest(config: Config, path: py.path.local, parent) -> bool: if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): return True globs = config.getoption("doctestglob") or ["test*.txt"] @@ -115,11 +150,12 @@ def _is_doctest(config, path, parent): class ReprFailDoctest(TerminalRepr): - def __init__(self, reprlocation_lines): - # List of (reprlocation, lines) tuples + def __init__( + self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] + ) -> None: self.reprlocation_lines = reprlocation_lines - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter) -> None: for reprlocation, lines in self.reprlocation_lines: for line in lines: tw.line(line) @@ -127,38 +163,53 @@ class ReprFailDoctest(TerminalRepr): class MultipleDoctestFailures(Exception): - def __init__(self, failures): - super(MultipleDoctestFailures, self).__init__() + def __init__(self, failures: "Sequence[doctest.DocTestFailure]") -> None: + super().__init__() self.failures = failures -def _init_runner_class(): +def _init_runner_class() -> "Type[doctest.DocTestRunner]": import doctest class PytestDoctestRunner(doctest.DebugRunner): - """ - Runner to collect failures. Note that the out variable in this case is - a list instead of a stdout-like object + """Runner to collect failures. + + Note that the out variable in this case is a list instead of a + stdout-like object. """ def __init__( - self, checker=None, verbose=None, optionflags=0, continue_on_failure=True - ): + self, + checker: Optional[doctest.OutputChecker] = None, + verbose: Optional[bool] = None, + optionflags: int = 0, + continue_on_failure: bool = True, + ) -> None: doctest.DebugRunner.__init__( self, checker=checker, verbose=verbose, optionflags=optionflags ) self.continue_on_failure = continue_on_failure - def report_failure(self, out, test, example, got): + def report_failure( + self, out, test: "doctest.DocTest", example: "doctest.Example", got: str, + ) -> None: failure = doctest.DocTestFailure(test, example, got) if self.continue_on_failure: out.append(failure) else: raise failure - def report_unexpected_exception(self, out, test, example, exc_info): - if isinstance(exc_info[1], Skipped): + def report_unexpected_exception( + self, + out, + test: "doctest.DocTest", + example: "doctest.Example", + exc_info: "Tuple[Type[BaseException], BaseException, types.TracebackType]", + ) -> None: + if isinstance(exc_info[1], OutcomeException): raise exc_info[1] + if isinstance(exc_info[1], bdb.BdbQuit): + outcomes.exit("Quitting debugger") failure = doctest.UnexpectedException(test, example, exc_info) if self.continue_on_failure: out.append(failure) @@ -168,12 +219,19 @@ def _init_runner_class(): return PytestDoctestRunner -def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=True): +def _get_runner( + checker: Optional["doctest.OutputChecker"] = None, + verbose: Optional[bool] = None, + optionflags: int = 0, + continue_on_failure: bool = True, +) -> "doctest.DocTestRunner": # We need this in order to do a lazy import on doctest global RUNNER_CLASS if RUNNER_CLASS is None: RUNNER_CLASS = _init_runner_class() - return RUNNER_CLASS( + # Type ignored because the continue_on_failure argument is only defined on + # PytestDoctestRunner, which is lazily defined so can't be used as a type. + return RUNNER_CLASS( # type: ignore checker=checker, verbose=verbose, optionflags=optionflags, @@ -182,14 +240,33 @@ def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=T class DoctestItem(pytest.Item): - def __init__(self, name, parent, runner=None, dtest=None): - super(DoctestItem, self).__init__(name, parent) + def __init__( + self, + name: str, + parent: "Union[DoctestTextfile, DoctestModule]", + runner: Optional["doctest.DocTestRunner"] = None, + dtest: Optional["doctest.DocTest"] = None, + ) -> None: + super().__init__(name, parent) self.runner = runner self.dtest = dtest self.obj = None - self.fixture_request = None - - def setup(self): + self.fixture_request = None # type: Optional[FixtureRequest] + + @classmethod + def from_parent( # type: ignore + cls, + parent: "Union[DoctestTextfile, DoctestModule]", + *, + name: str, + runner: "doctest.DocTestRunner", + dtest: "doctest.DocTest" + ): + # incompatible signature due to to imposed limits on sublcass + """The public named constructor.""" + return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) + + def setup(self) -> None: if self.dtest is not None: self.fixture_request = _setup_fixtures(self) globs = dict(getfixture=self.fixture_request.getfixturevalue) @@ -199,18 +276,20 @@ class DoctestItem(pytest.Item): globs[name] = value self.dtest.globs.update(globs) - def runtest(self): + def runtest(self) -> None: + assert self.dtest is not None + assert self.runner is not None _check_all_skipped(self.dtest) self._disable_output_capturing_for_darwin() - failures = [] - self.runner.run(self.dtest, out=failures) + failures = [] # type: List[doctest.DocTestFailure] + # Type ignored because we change the type of `out` from what + # doctest expects. + self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] if failures: raise MultipleDoctestFailures(failures) - def _disable_output_capturing_for_darwin(self): - """ - Disable output capturing. Otherwise, stdout is lost to doctest (#985) - """ + def _disable_output_capturing_for_darwin(self) -> None: + """Disable output capturing. Otherwise, stdout is lost to doctest (#985).""" if platform.system() != "Darwin": return capman = self.config.pluginmanager.getplugin("capturemanager") @@ -220,13 +299,20 @@ class DoctestItem(pytest.Item): sys.stdout.write(out) sys.stderr.write(err) - def repr_failure(self, excinfo): + # TODO: Type ignored -- breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, excinfo: ExceptionInfo[BaseException], + ) -> Union[str, TerminalRepr]: import doctest - failures = None - if excinfo.errisinstance((doctest.DocTestFailure, doctest.UnexpectedException)): + failures = ( + None + ) # type: Optional[Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]] + if isinstance( + excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) + ): failures = [excinfo.value] - elif excinfo.errisinstance(MultipleDoctestFailures): + elif isinstance(excinfo.value, MultipleDoctestFailures): failures = excinfo.value.failures if failures is not None: @@ -240,14 +326,17 @@ class DoctestItem(pytest.Item): else: lineno = test.lineno + example.lineno + 1 message = type(failure).__name__ - reprlocation = ReprFileLocation(filename, lineno, message) + # TODO: ReprFileLocation doesn't expect a None lineno. + reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] checker = _get_checker() report_choice = _get_report_choice( self.config.getoption("doctestreport") ) if lineno is not None: + assert failure.test.docstring is not None lines = failure.test.docstring.splitlines(False) # add line numbers to the left of the error message + assert test.lineno is not None lines = [ "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines) @@ -260,7 +349,7 @@ class DoctestItem(pytest.Item): ] indent = ">>>" for line in example.source.splitlines(): - lines.append("??? %s %s" % (indent, line)) + lines.append("??? {} {}".format(indent, line)) indent = "..." if isinstance(failure, doctest.DocTestFailure): lines += checker.output_difference( @@ -269,17 +358,21 @@ class DoctestItem(pytest.Item): else: inner_excinfo = ExceptionInfo(failure.exc_info) lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] - lines += traceback.format_exception(*failure.exc_info) + lines += [ + x.strip("\n") + for x in traceback.format_exception(*failure.exc_info) + ] reprlocation_lines.append((reprlocation, lines)) return ReprFailDoctest(reprlocation_lines) else: - return super(DoctestItem, self).repr_failure(excinfo) + return super().repr_failure(excinfo) def reportinfo(self): + assert self.dtest is not None return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name -def _get_flag_lookup(): +def _get_flag_lookup() -> Dict[str, int]: import doctest return dict( @@ -291,6 +384,7 @@ def _get_flag_lookup(): COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, ALLOW_UNICODE=_get_allow_unicode_flag(), ALLOW_BYTES=_get_allow_bytes_flag(), + NUMBER=_get_number_flag(), ) @@ -307,7 +401,7 @@ def _get_continue_on_failure(config): continue_on_failure = config.getvalue("doctest_continue_on_failure") if continue_on_failure: # We need to turn off this if we use pdb since we should stop at - # the first failure + # the first failure. if config.getvalue("usepdb"): continue_on_failure = False return continue_on_failure @@ -316,11 +410,11 @@ def _get_continue_on_failure(config): class DoctestTextfile(pytest.Module): obj = None - def collect(self): + def collect(self) -> Iterable[DoctestItem]: import doctest - # inspired by doctest.testfile; ideally we would use it directly, - # but it doesn't support passing a custom checker + # Inspired by doctest.testfile; ideally we would use it directly, + # but it doesn't support passing a custom checker. encoding = self.config.getini("doctest_encoding") text = self.fspath.read_text(encoding) filename = str(self.fspath) @@ -330,23 +424,23 @@ class DoctestTextfile(pytest.Module): optionflags = get_optionflags(self) runner = _get_runner( - verbose=0, + verbose=False, optionflags=optionflags, checker=_get_checker(), continue_on_failure=_get_continue_on_failure(self.config), ) - _fix_spoof_python2(runner, encoding) parser = doctest.DocTestParser() test = parser.get_doctest(text, globs, name, filename, 0) if test.examples: - yield DoctestItem(test.name, self, runner, test) + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) -def _check_all_skipped(test): - """raises pytest.skip() if all examples in the given DocTest have the SKIP - option set. - """ +def _check_all_skipped(test: "doctest.DocTest") -> None: + """Raise pytest.skip() if all examples in the given DocTest have the SKIP + option set.""" import doctest all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) @@ -354,10 +448,9 @@ def _check_all_skipped(test): pytest.skip("all tests skipped by +SKIP option") -def _is_mocked(obj): - """ - returns if a object is possibly a mock object by checking the existence of a highly improbable attribute - """ +def _is_mocked(obj: object) -> bool: + """Return if an object is possibly a mock object by checking the + existence of a highly improbable attribute.""" return ( safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) is not None @@ -365,73 +458,88 @@ def _is_mocked(obj): @contextmanager -def _patch_unwrap_mock_aware(): - """ - contextmanager which replaces ``inspect.unwrap`` with a version - that's aware of mock objects and doesn't recurse on them - """ - real_unwrap = getattr(inspect, "unwrap", None) - if real_unwrap is None: - yield - else: - - def _mock_aware_unwrap(obj, stop=None): - try: - if stop is None or stop is _is_mocked: - return real_unwrap(obj, stop=_is_mocked) - return real_unwrap(obj, stop=lambda obj: _is_mocked(obj) or stop(obj)) - except Exception as e: - warnings.warn( - "Got %r when unwrapping %r. This is usually caused " - "by a violation of Python's object protocol; see e.g. " - "https://github.com/pytest-dev/pytest/issues/5080" % (e, obj), - PytestWarning, - ) - raise - - inspect.unwrap = _mock_aware_unwrap +def _patch_unwrap_mock_aware() -> Generator[None, None, None]: + """Context manager which replaces ``inspect.unwrap`` with a version + that's aware of mock objects and doesn't recurse into them.""" + real_unwrap = inspect.unwrap + + def _mock_aware_unwrap( + func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None + ) -> Any: try: - yield - finally: - inspect.unwrap = real_unwrap + if stop is None or stop is _is_mocked: + return real_unwrap(func, stop=_is_mocked) + _stop = stop + return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) + except Exception as e: + warnings.warn( + "Got %r when unwrapping %r. This is usually caused " + "by a violation of Python's object protocol; see e.g. " + "https://github.com/pytest-dev/pytest/issues/5080" % (e, func), + PytestWarning, + ) + raise + + inspect.unwrap = _mock_aware_unwrap + try: + yield + finally: + inspect.unwrap = real_unwrap class DoctestModule(pytest.Module): - def collect(self): + def collect(self) -> Iterable[DoctestItem]: import doctest class MockAwareDocTestFinder(doctest.DocTestFinder): - """ - a hackish doctest finder that overrides stdlib internals to fix a stdlib bug + """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. https://github.com/pytest-dev/pytest/issues/3456 https://bugs.python.org/issue25532 """ - def _find(self, tests, obj, name, module, source_lines, globs, seen): + def _find_lineno(self, obj, source_lines): + """Doctest code does not take into account `@property`, this + is a hackish way to fix it. + + https://bugs.python.org/issue17446 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + # Type ignored because this is a private function. + return doctest.DocTestFinder._find_lineno( # type: ignore + self, obj, source_lines, + ) + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> None: if _is_mocked(obj): return with _patch_unwrap_mock_aware(): - doctest.DocTestFinder._find( + # Type ignored because this is a private function. + doctest.DocTestFinder._find( # type: ignore self, tests, obj, name, module, source_lines, globs, seen ) if self.fspath.basename == "conftest.py": - module = self.config.pluginmanager._importconftest(self.fspath) + module = self.config.pluginmanager._importconftest( + self.fspath, self.config.getoption("importmode") + ) else: try: - module = self.fspath.pyimport() + module = import_path(self.fspath) except ImportError: if self.config.getvalue("doctest_ignore_import_errors"): pytest.skip("unable to import module %r" % self.fspath) else: raise - # uses internal doctest module parsing mechanism + # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() optionflags = get_optionflags(self) runner = _get_runner( - verbose=0, + verbose=False, optionflags=optionflags, checker=_get_checker(), continue_on_failure=_get_continue_on_failure(self.config), @@ -439,20 +547,20 @@ class DoctestModule(pytest.Module): for test in finder.find(module, module.__name__): if test.examples: # skip empty doctests - yield DoctestItem(test.name, self, runner, test) + yield DoctestItem.from_parent( + self, name=test.name, runner=runner, dtest=test + ) -def _setup_fixtures(doctest_item): - """ - Used by DoctestTextfile and DoctestItem to setup fixture information. - """ +def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: + """Used by DoctestTextfile and DoctestItem to setup fixture information.""" - def func(): + def func() -> None: pass - doctest_item.funcargs = {} + doctest_item.funcargs = {} # type: ignore[attr-defined] fm = doctest_item.session._fixturemanager - doctest_item._fixtureinfo = fm.getfixtureinfo( + doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] node=doctest_item, func=func, cls=None, funcargs=False ) fixture_request = FixtureRequest(doctest_item) @@ -460,83 +568,143 @@ def _setup_fixtures(doctest_item): return fixture_request -def _get_checker(): - """ - Returns a doctest.OutputChecker subclass that takes in account the - ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES - to strip b'' prefixes. - Useful when the same doctest should run in Python 2 and Python 3. - - An inner class is used to avoid importing "doctest" at the module - level. - """ - if hasattr(_get_checker, "LiteralsOutputChecker"): - return _get_checker.LiteralsOutputChecker() - +def _init_checker_class() -> "Type[doctest.OutputChecker]": import doctest import re class LiteralsOutputChecker(doctest.OutputChecker): - """ - Copied from doctest_nose_plugin.py from the nltk project: - https://github.com/nltk/nltk - - Further extended to also support byte literals. - """ + # Based on doctest_nose_plugin.py from the nltk project + # (https://github.com/nltk/nltk) and on the "numtest" doctest extension + # by Sebastien Boisgerault (https://github.com/boisgera/numtest). _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) + _number_re = re.compile( + r""" + (?P<number> + (?P<mantissa> + (?P<integer1> [+-]?\d*)\.(?P<fraction>\d+) + | + (?P<integer2> [+-]?\d+)\. + ) + (?: + [Ee] + (?P<exponent1> [+-]?\d+) + )? + | + (?P<integer3> [+-]?\d+) + (?: + [Ee] + (?P<exponent2> [+-]?\d+) + ) + ) + """, + re.VERBOSE, + ) - def check_output(self, want, got, optionflags): - res = doctest.OutputChecker.check_output(self, want, got, optionflags) - if res: + def check_output(self, want: str, got: str, optionflags: int) -> bool: + if doctest.OutputChecker.check_output(self, want, got, optionflags): return True allow_unicode = optionflags & _get_allow_unicode_flag() allow_bytes = optionflags & _get_allow_bytes_flag() - if not allow_unicode and not allow_bytes: + allow_number = optionflags & _get_number_flag() + + if not allow_unicode and not allow_bytes and not allow_number: return False - else: # pragma: no cover + def remove_prefixes(regex: Pattern[str], txt: str) -> str: + return re.sub(regex, r"\1\2", txt) + + if allow_unicode: + want = remove_prefixes(self._unicode_literal_re, want) + got = remove_prefixes(self._unicode_literal_re, got) + + if allow_bytes: + want = remove_prefixes(self._bytes_literal_re, want) + got = remove_prefixes(self._bytes_literal_re, got) + + if allow_number: + got = self._remove_unwanted_precision(want, got) + + return doctest.OutputChecker.check_output(self, want, got, optionflags) + + def _remove_unwanted_precision(self, want: str, got: str) -> str: + wants = list(self._number_re.finditer(want)) + gots = list(self._number_re.finditer(got)) + if len(wants) != len(gots): + return got + offset = 0 + for w, g in zip(wants, gots): + fraction = w.group("fraction") # type: Optional[str] + exponent = w.group("exponent1") # type: Optional[str] + if exponent is None: + exponent = w.group("exponent2") + if fraction is None: + precision = 0 + else: + precision = len(fraction) + if exponent is not None: + precision -= int(exponent) + if float(w.group()) == approx(float(g.group()), abs=10 ** -precision): + # They're close enough. Replace the text we actually + # got with the text we want, so that it will match when we + # check the string literally. + got = ( + got[: g.start() + offset] + w.group() + got[g.end() + offset :] + ) + offset += w.end() - w.start() - (g.end() - g.start()) + return got - def remove_prefixes(regex, txt): - return re.sub(regex, r"\1\2", txt) + return LiteralsOutputChecker - if allow_unicode: - want = remove_prefixes(self._unicode_literal_re, want) - got = remove_prefixes(self._unicode_literal_re, got) - if allow_bytes: - want = remove_prefixes(self._bytes_literal_re, want) - got = remove_prefixes(self._bytes_literal_re, got) - res = doctest.OutputChecker.check_output(self, want, got, optionflags) - return res - _get_checker.LiteralsOutputChecker = LiteralsOutputChecker - return _get_checker.LiteralsOutputChecker() +def _get_checker() -> "doctest.OutputChecker": + """Return a doctest.OutputChecker subclass that supports some + additional options: + * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' + prefixes (respectively) in string literals. Useful when the same + doctest should run in Python 2 and Python 3. -def _get_allow_unicode_flag(): - """ - Registers and returns the ALLOW_UNICODE flag. + * NUMBER to ignore floating-point differences smaller than the + precision of the literal number in the doctest. + + An inner class is used to avoid importing "doctest" at the module + level. """ + global CHECKER_CLASS + if CHECKER_CLASS is None: + CHECKER_CLASS = _init_checker_class() + return CHECKER_CLASS() + + +def _get_allow_unicode_flag() -> int: + """Register and return the ALLOW_UNICODE flag.""" import doctest return doctest.register_optionflag("ALLOW_UNICODE") -def _get_allow_bytes_flag(): - """ - Registers and returns the ALLOW_BYTES flag. - """ +def _get_allow_bytes_flag() -> int: + """Register and return the ALLOW_BYTES flag.""" import doctest return doctest.register_optionflag("ALLOW_BYTES") -def _get_report_choice(key): - """ - This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid - importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests. +def _get_number_flag() -> int: + """Register and return the NUMBER flag.""" + import doctest + + return doctest.register_optionflag("NUMBER") + + +def _get_report_choice(key: str) -> int: + """Return the actual `doctest` module flag value. + + We want to do it as late as possible to avoid importing `doctest` and all + its dependencies when parsing options, as it adds overhead and breaks tests. """ import doctest @@ -549,35 +717,8 @@ def _get_report_choice(key): }[key] -def _fix_spoof_python2(runner, encoding): - """ - Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This - should patch only doctests for text files because they don't have a way to declare their - encoding. Doctests in docstrings from Python modules don't have the same problem given that - Python already decoded the strings. - - This fixes the problem related in issue #2434. - """ - from _pytest.compat import _PY2 - - if not _PY2: - return - - from doctest import _SpoofOut - - class UnicodeSpoof(_SpoofOut): - def getvalue(self): - result = _SpoofOut.getvalue(self) - if encoding and isinstance(result, bytes): - result = result.decode(encoding) - return result - - runner._fakeout = UnicodeSpoof() - - @pytest.fixture(scope="session") -def doctest_namespace(): - """ - Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. - """ +def doctest_namespace() -> Dict[str, Any]: + """Fixture that returns a :py:class:`dict` that will be injected into the + namespace of doctests.""" return dict() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py new file mode 100644 index 00000000000..d0cc0430c49 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/faulthandler.py @@ -0,0 +1,111 @@ +import io +import os +import sys +from typing import Generator +from typing import TextIO + +import pytest +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.nodes import Item +from _pytest.store import StoreKey + + +fault_handler_stderr_key = StoreKey[TextIO]() + + +def pytest_addoption(parser: Parser) -> None: + help = ( + "Dump the traceback of all threads if a test takes " + "more than TIMEOUT seconds to finish." + ) + parser.addini("faulthandler_timeout", help, default=0.0) + + +def pytest_configure(config: Config) -> None: + import faulthandler + + if not faulthandler.is_enabled(): + # faulthhandler is not enabled, so install plugin that does the actual work + # of enabling faulthandler before each test executes. + config.pluginmanager.register(FaultHandlerHooks(), "faulthandler-hooks") + else: + # Do not handle dumping to stderr if faulthandler is already enabled, so warn + # users that the option is being ignored. + timeout = FaultHandlerHooks.get_timeout_config_value(config) + if timeout > 0: + config.issue_config_time_warning( + pytest.PytestConfigWarning( + "faulthandler module enabled before pytest configuration step, " + "'faulthandler_timeout' option ignored" + ), + stacklevel=2, + ) + + +class FaultHandlerHooks: + """Implements hooks that will actually install fault handler before tests execute, + as well as correctly handle pdb and internal errors.""" + + def pytest_configure(self, config: Config) -> None: + import faulthandler + + stderr_fd_copy = os.dup(self._get_stderr_fileno()) + config._store[fault_handler_stderr_key] = open(stderr_fd_copy, "w") + faulthandler.enable(file=config._store[fault_handler_stderr_key]) + + def pytest_unconfigure(self, config: Config) -> None: + import faulthandler + + faulthandler.disable() + # close our dup file installed during pytest_configure + # re-enable the faulthandler, attaching it to the default sys.stderr + # so we can see crashes after pytest has finished, usually during + # garbage collection during interpreter shutdown + config._store[fault_handler_stderr_key].close() + del config._store[fault_handler_stderr_key] + faulthandler.enable(file=self._get_stderr_fileno()) + + @staticmethod + def _get_stderr_fileno(): + try: + return sys.stderr.fileno() + except (AttributeError, io.UnsupportedOperation): + # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file. + # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors + # This is potentially dangerous, but the best we can do. + return sys.__stderr__.fileno() + + @staticmethod + def get_timeout_config_value(config): + return float(config.getini("faulthandler_timeout") or 0.0) + + @pytest.hookimpl(hookwrapper=True, trylast=True) + def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]: + timeout = self.get_timeout_config_value(item.config) + stderr = item.config._store[fault_handler_stderr_key] + if timeout > 0 and stderr is not None: + import faulthandler + + faulthandler.dump_traceback_later(timeout, file=stderr) + try: + yield + finally: + faulthandler.cancel_dump_traceback_later() + else: + yield + + @pytest.hookimpl(tryfirst=True) + def pytest_enter_pdb(self) -> None: + """Cancel any traceback dumping due to timeout before entering pdb.""" + import faulthandler + + faulthandler.cancel_dump_traceback_later() + + @pytest.hookimpl(tryfirst=True) + def pytest_exception_interact(self) -> None: + """Cancel any traceback dumping due to an interactive exception being + raised.""" + import faulthandler + + faulthandler.cancel_dump_traceback_later() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py index 280a48608b6..f526f484b29 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/fixtures.py @@ -1,52 +1,108 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import functools import inspect -import itertools +import os import sys import warnings from collections import defaultdict from collections import deque -from collections import OrderedDict +from types import TracebackType +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import Generator +from typing import Generic +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TypeVar +from typing import Union import attr import py -import six import _pytest -from _pytest import nodes +from _pytest._code import getfslineno from _pytest._code.code import FormattedExcinfo from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter from _pytest.compat import _format_args from _pytest.compat import _PytestWrapper -from _pytest.compat import exc_clear -from _pytest.compat import FuncargnamesCompatAttr +from _pytest.compat import final from _pytest.compat import get_real_func from _pytest.compat import get_real_method -from _pytest.compat import getfslineno from _pytest.compat import getfuncargnames from _pytest.compat import getimfunc from _pytest.compat import getlocation from _pytest.compat import is_generator -from _pytest.compat import isclass from _pytest.compat import NOTSET +from _pytest.compat import order_preserving_dict +from _pytest.compat import overload from _pytest.compat import safe_getattr -from _pytest.deprecated import FIXTURE_FUNCTION_CALL -from _pytest.deprecated import FIXTURE_NAMED_REQUEST +from _pytest.compat import TYPE_CHECKING +from _pytest.config import _PluggyPlugin +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.deprecated import FILLFUNCARGS +from _pytest.mark import Mark +from _pytest.mark import ParameterSet from _pytest.outcomes import fail from _pytest.outcomes import TEST_OUTCOME +from _pytest.pathlib import absolutepath + +if TYPE_CHECKING: + from typing import Deque + from typing import NoReturn + from typing import Type + from typing_extensions import Literal + + from _pytest import nodes + from _pytest.main import Session + from _pytest.python import CallSpec2 + from _pytest.python import Function + from _pytest.python import Metafunc + + _Scope = Literal["session", "package", "module", "class", "function"] + + +# The value of the fixture -- return/yield of the fixture function (type variable). +_FixtureValue = TypeVar("_FixtureValue") +# The type of the fixture function (type variable). +_FixtureFunction = TypeVar("_FixtureFunction", bound=Callable[..., object]) +# The type of a fixture function (type alias generic in fixture value). +_FixtureFunc = Union[ + Callable[..., _FixtureValue], Callable[..., Generator[_FixtureValue, None, None]] +] +# The type of FixtureDef.cached_result (type alias generic in fixture value). +_FixtureCachedResult = Union[ + Tuple[ + # The result. + _FixtureValue, + # Cache key. + object, + None, + ], + Tuple[ + None, + # Cache key. + object, + # Exc info if raised. + Tuple["Type[BaseException]", BaseException, TracebackType], + ], +] @attr.s(frozen=True) -class PseudoFixtureDef(object): - cached_result = attr.ib() - scope = attr.ib() +class PseudoFixtureDef(Generic[_FixtureValue]): + cached_result = attr.ib(type="_FixtureCachedResult[_FixtureValue]") + scope = attr.ib(type="_Scope") -def pytest_sessionstart(session): +def pytest_sessionstart(session: "Session") -> None: import _pytest.python import _pytest.nodes @@ -62,39 +118,15 @@ def pytest_sessionstart(session): session._fixturemanager = FixtureManager(session) -scopename2class = {} - - -scope2props = dict(session=()) -scope2props["package"] = ("fspath",) -scope2props["module"] = ("fspath", "module") -scope2props["class"] = scope2props["module"] + ("cls",) -scope2props["instance"] = scope2props["class"] + ("instance",) -scope2props["function"] = scope2props["instance"] + ("function", "keywords") - - -def scopeproperty(name=None, doc=None): - def decoratescope(func): - scopename = name or func.__name__ - - def provide(self): - if func.__name__ in scope2props[self.scope]: - return func(self) - raise AttributeError( - "%s not available in %s-scoped context" % (scopename, self.scope) - ) - - return property(provide, None, None, func.__doc__) +scopename2class = {} # type: Dict[str, Type[nodes.Node]] - return decoratescope - -def get_scope_package(node, fixturedef): +def get_scope_package(node, fixturedef: "FixtureDef[object]"): import pytest cls = pytest.Package current = node - fixture_package_name = "%s/%s" % (fixturedef.baseid, "__init__.py") + fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py") while current and ( type(current) is not cls or fixture_package_name != current.nodeid ): @@ -111,18 +143,21 @@ def get_scope_node(node, scope): return node.getparent(cls) -def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): - # this function will transform all collected calls to a functions +def add_funcarg_pseudo_fixture_def( + collector, metafunc: "Metafunc", fixturemanager: "FixtureManager" +) -> None: + # This function will transform all collected calls to functions # if they use direct funcargs (i.e. direct parametrization) # because we want later test execution to be able to rely on # an existing FixtureDef structure for all arguments. # XXX we can probably avoid this algorithm if we modify CallSpec2 # to directly care for creating the fixturedefs within its methods. if not metafunc._calls[0].funcargs: - return # this function call does not have direct parametrization - # collect funcargs of all callspecs into a list of values - arg2params = {} - arg2scope = {} + # This function call does not have direct parametrization. + return + # Collect funcargs of all callspecs into a list of values. + arg2params = {} # type: Dict[str, List[object]] + arg2scope = {} # type: Dict[str, _Scope] for callspec in metafunc._calls: for argname, argvalue in callspec.funcargs.items(): assert argname not in callspec.params @@ -135,11 +170,11 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): arg2scope[argname] = scopes[scopenum] callspec.funcargs.clear() - # register artificial FixtureDef's so that later at test execution + # Register artificial FixtureDef's so that later at test execution # time we can rely on a proper FixtureDef to exist for fixture setup. arg2fixturedefs = metafunc._arg2fixturedefs for argname, valuelist in arg2params.items(): - # if we have a scope that is higher than function we need + # If we have a scope that is higher than function, we need # to make sure we only ever create an according fixturedef on # a per-scope basis. We thus store and cache the fixturedef on the # node related to the scope. @@ -149,46 +184,54 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): node = get_scope_node(collector, scope) if node is None: assert scope == "class" and isinstance(collector, _pytest.python.Module) - # use module-level collector for class-scope (for now) + # Use module-level collector for class-scope (for now). node = collector if node and argname in node._name2pseudofixturedef: arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]] else: fixturedef = FixtureDef( - fixturemanager, - "", - argname, - get_direct_param_fixture_func, - arg2scope[argname], - valuelist, - False, - False, + fixturemanager=fixturemanager, + baseid="", + argname=argname, + func=get_direct_param_fixture_func, + scope=arg2scope[argname], + params=valuelist, + unittest=False, + ids=None, ) arg2fixturedefs[argname] = [fixturedef] if node is not None: node._name2pseudofixturedef[argname] = fixturedef -def getfixturemarker(obj): - """ return fixturemarker or None if it doesn't exist or raised +def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: + """Return fixturemarker or None if it doesn't exist or raised exceptions.""" try: - return getattr(obj, "_pytestfixturefunction", None) + fixturemarker = getattr( + obj, "_pytestfixturefunction", None + ) # type: Optional[FixtureFunctionMarker] except TEST_OUTCOME: # some objects raise errors like request (from flask import request) # we don't expect them to be fixture functions return None + return fixturemarker + +# Parametrized fixture key, helper alias for code below. +_Key = Tuple[object, ...] -def get_parametrized_fixture_keys(item, scopenum): - """ return list of keys for all parametrized arguments which match + +def get_parametrized_fixture_keys(item: "nodes.Item", scopenum: int) -> Iterator[_Key]: + """Return list of keys for all parametrized arguments which match the specified scope. """ assert scopenum < scopenum_function # function try: - cs = item.callspec + callspec = item.callspec # type: ignore[attr-defined] except AttributeError: pass else: + cs = callspec # type: CallSpec2 # cs.indices.items() is random order of argnames. Need to # sort this so that different calls to # get_parametrized_fixture_keys will be deterministic. @@ -196,67 +239,89 @@ def get_parametrized_fixture_keys(item, scopenum): if cs._arg2scopenum[argname] != scopenum: continue if scopenum == 0: # session - key = (argname, param_index) + key = (argname, param_index) # type: _Key elif scopenum == 1: # package key = (argname, param_index, item.fspath.dirpath()) elif scopenum == 2: # module key = (argname, param_index, item.fspath) elif scopenum == 3: # class - key = (argname, param_index, item.fspath, item.cls) + item_cls = item.cls # type: ignore[attr-defined] + key = (argname, param_index, item.fspath, item_cls) yield key -# algorithm for sorting on a per-parametrized resource setup basis -# it is called for scopenum==0 (session) first and performs sorting +# Algorithm for sorting on a per-parametrized resource setup basis. +# It is called for scopenum==0 (session) first and performs sorting # down to the lower scopes such as to minimize number of "high scope" -# setups and teardowns +# setups and teardowns. -def reorder_items(items): - argkeys_cache = {} - items_by_argkey = {} +def reorder_items(items: "Sequence[nodes.Item]") -> "List[nodes.Item]": + argkeys_cache = {} # type: Dict[int, Dict[nodes.Item, Dict[_Key, None]]] + items_by_argkey = {} # type: Dict[int, Dict[_Key, Deque[nodes.Item]]] for scopenum in range(0, scopenum_function): - argkeys_cache[scopenum] = d = {} - items_by_argkey[scopenum] = item_d = defaultdict(deque) + d = {} # type: Dict[nodes.Item, Dict[_Key, None]] + argkeys_cache[scopenum] = d + item_d = defaultdict(deque) # type: Dict[_Key, Deque[nodes.Item]] + items_by_argkey[scopenum] = item_d for item in items: - keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum)) + # cast is a workaround for https://github.com/python/typeshed/issues/3800. + keys = cast( + "Dict[_Key, None]", + order_preserving_dict.fromkeys( + get_parametrized_fixture_keys(item, scopenum), None + ), + ) if keys: d[item] = keys for key in keys: item_d[key].append(item) - items = OrderedDict.fromkeys(items) - return list(reorder_items_atscope(items, argkeys_cache, items_by_argkey, 0)) + # cast is a workaround for https://github.com/python/typeshed/issues/3800. + items_dict = cast( + "Dict[nodes.Item, None]", order_preserving_dict.fromkeys(items, None) + ) + return list(reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, 0)) -def fix_cache_order(item, argkeys_cache, items_by_argkey): +def fix_cache_order( + item: "nodes.Item", + argkeys_cache: "Dict[int, Dict[nodes.Item, Dict[_Key, None]]]", + items_by_argkey: "Dict[int, Dict[_Key, Deque[nodes.Item]]]", +) -> None: for scopenum in range(0, scopenum_function): for key in argkeys_cache[scopenum].get(item, []): items_by_argkey[scopenum][key].appendleft(item) -def reorder_items_atscope(items, argkeys_cache, items_by_argkey, scopenum): +def reorder_items_atscope( + items: "Dict[nodes.Item, None]", + argkeys_cache: "Dict[int, Dict[nodes.Item, Dict[_Key, None]]]", + items_by_argkey: "Dict[int, Dict[_Key, Deque[nodes.Item]]]", + scopenum: int, +) -> "Dict[nodes.Item, None]": if scopenum >= scopenum_function or len(items) < 3: return items - ignore = set() + ignore = set() # type: Set[Optional[_Key]] items_deque = deque(items) - items_done = OrderedDict() + items_done = order_preserving_dict() # type: Dict[nodes.Item, None] scoped_items_by_argkey = items_by_argkey[scopenum] scoped_argkeys_cache = argkeys_cache[scopenum] while items_deque: - no_argkey_group = OrderedDict() + no_argkey_group = order_preserving_dict() # type: Dict[nodes.Item, None] slicing_argkey = None while items_deque: item = items_deque.popleft() if item in items_done or item in no_argkey_group: continue - argkeys = OrderedDict.fromkeys( - k for k in scoped_argkeys_cache.get(item, []) if k not in ignore + argkeys = order_preserving_dict.fromkeys( + (k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None ) if not argkeys: no_argkey_group[item] = None else: slicing_argkey, _ = argkeys.popitem() - # we don't have to remove relevant items from later in the deque because they'll just be ignored + # We don't have to remove relevant items from later in the + # deque because they'll just be ignored. matching_items = [ i for i in scoped_items_by_argkey[slicing_argkey] if i in items ] @@ -274,8 +339,9 @@ def reorder_items_atscope(items, argkeys_cache, items_by_argkey, scopenum): return items_done -def fillfixtures(function): - """ fill missing funcargs for a test function. """ +def fillfixtures(function: "Function") -> None: + """Fill missing funcargs for a test function.""" + warnings.warn(FILLFUNCARGS, stacklevel=2) try: request = function._request except AttributeError: @@ -283,11 +349,12 @@ def fillfixtures(function): # with the oejskit plugin. It uses classes with funcargs # and we thus have to work a bit to allow this. fm = function.session._fixturemanager + assert function.parent is not None fi = fm.getfixtureinfo(function.parent, function.obj, None) function._fixtureinfo = fi request = function._request = FixtureRequest(function) request._fillfixtures() - # prune out funcargs for jstests + # Prune out funcargs for jstests. newfuncargs = {} for name in fi.argnames: newfuncargs[name] = function.funcargs[name] @@ -301,18 +368,18 @@ def get_direct_param_fixture_func(request): @attr.s(slots=True) -class FuncFixtureInfo(object): - # original function argument names - argnames = attr.ib(type=tuple) - # argnames that function immediately requires. These include argnames + +class FuncFixtureInfo: + # Original function argument names. + argnames = attr.ib(type=Tuple[str, ...]) + # Argnames that function immediately requires. These include argnames + # fixture names specified via usefixtures and via autouse=True in fixture # definitions. - initialnames = attr.ib(type=tuple) - names_closure = attr.ib() # List[str] - name2fixturedefs = attr.ib() # List[str, List[FixtureDef]] + initialnames = attr.ib(type=Tuple[str, ...]) + names_closure = attr.ib(type=List[str]) + name2fixturedefs = attr.ib(type=Dict[str, Sequence["FixtureDef[Any]"]]) - def prune_dependency_tree(self): - """Recompute names_closure from initialnames and name2fixturedefs + def prune_dependency_tree(self) -> None: + """Recompute names_closure from initialnames and name2fixturedefs. Can only reduce names_closure, which means that the new closure will always be a subset of the old one. The order is preserved. @@ -322,11 +389,11 @@ class FuncFixtureInfo(object): tree. In this way the dependency tree can get pruned, and the closure of argnames may get reduced. """ - closure = set() + closure = set() # type: Set[str] working_set = set(self.initialnames) while working_set: argname = working_set.pop() - # argname may be smth not included in the original names_closure, + # Argname may be smth not included in the original names_closure, # in which case we ignore it. This currently happens with pseudo # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'. # So they introduce the new dependency 'request' which might have @@ -339,48 +406,53 @@ class FuncFixtureInfo(object): self.names_closure[:] = sorted(closure, key=self.names_closure.index) -class FixtureRequest(FuncargnamesCompatAttr): - """ A request for a fixture from a test or fixture function. +class FixtureRequest: + """A request for a fixture from a test or fixture function. - A request object gives access to the requesting test context - and has an optional ``param`` attribute in case - the fixture is parametrized indirectly. + A request object gives access to the requesting test context and has + an optional ``param`` attribute in case the fixture is parametrized + indirectly. """ - def __init__(self, pyfuncitem): + def __init__(self, pyfuncitem) -> None: self._pyfuncitem = pyfuncitem - #: fixture for which this request is being performed - self.fixturename = None - #: Scope string, one of "function", "class", "module", "session" - self.scope = "function" - self._fixture_defs = {} # argname -> FixtureDef - fixtureinfo = pyfuncitem._fixtureinfo + #: Fixture for which this request is being performed. + self.fixturename = None # type: Optional[str] + #: Scope string, one of "function", "class", "module", "session". + self.scope = "function" # type: _Scope + self._fixture_defs = {} # type: Dict[str, FixtureDef[Any]] + fixtureinfo = pyfuncitem._fixtureinfo # type: FuncFixtureInfo self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() - self._arg2index = {} - self._fixturemanager = pyfuncitem.session._fixturemanager + self._arg2index = {} # type: Dict[str, int] + self._fixturemanager = ( + pyfuncitem.session._fixturemanager + ) # type: FixtureManager @property - def fixturenames(self): - """names of all active fixtures in this request""" + def fixturenames(self) -> List[str]: + """Names of all active fixtures in this request.""" result = list(self._pyfuncitem._fixtureinfo.names_closure) result.extend(set(self._fixture_defs).difference(result)) return result @property def node(self): - """ underlying collection node (depends on current request scope)""" + """Underlying collection node (depends on current request scope).""" return self._getscopeitem(self.scope) - def _getnextfixturedef(self, argname): + def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]": fixturedefs = self._arg2fixturedefs.get(argname, None) if fixturedefs is None: - # we arrive here because of a dynamic call to + # We arrive here because of a dynamic call to # getfixturevalue(argname) usage which was naturally - # not known at parsing/collection time + # not known at parsing/collection time. + assert self._pyfuncitem.parent is not None parentid = self._pyfuncitem.parent.nodeid fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) - self._arg2fixturedefs[argname] = fixturedefs - # fixturedefs list is immutable so we maintain a decreasing index + # TODO: Fix this type ignore. Either add assert or adjust types. + # Can this be None here? + self._arg2fixturedefs[argname] = fixturedefs # type: ignore[assignment] + # fixturedefs list is immutable so we maintain a decreasing index. index = self._arg2index.get(argname, 0) - 1 if fixturedefs is None or (-index > len(fixturedefs)): raise FixtureLookupError(argname, self) @@ -388,104 +460,122 @@ class FixtureRequest(FuncargnamesCompatAttr): return fixturedefs[index] @property - def config(self): - """ the pytest config object associated with this request. """ - return self._pyfuncitem.config + def config(self) -> Config: + """The pytest config object associated with this request.""" + return self._pyfuncitem.config # type: ignore[no-any-return] # noqa: F723 - @scopeproperty() + @property def function(self): - """ test function object if the request has a per-function scope. """ + """Test function object if the request has a per-function scope.""" + if self.scope != "function": + raise AttributeError( + "function not available in {}-scoped context".format(self.scope) + ) return self._pyfuncitem.obj - @scopeproperty("class") + @property def cls(self): - """ class (can be None) where the test function was collected. """ + """Class (can be None) where the test function was collected.""" + if self.scope not in ("class", "function"): + raise AttributeError( + "cls not available in {}-scoped context".format(self.scope) + ) clscol = self._pyfuncitem.getparent(_pytest.python.Class) if clscol: return clscol.obj @property def instance(self): - """ instance (can be None) on which test function was collected. """ - # unittest support hack, see _pytest.unittest.TestCaseFunction + """Instance (can be None) on which test function was collected.""" + # unittest support hack, see _pytest.unittest.TestCaseFunction. try: return self._pyfuncitem._testcase except AttributeError: function = getattr(self, "function", None) return getattr(function, "__self__", None) - @scopeproperty() + @property def module(self): - """ python module object where the test function was collected. """ + """Python module object where the test function was collected.""" + if self.scope not in ("function", "class", "module"): + raise AttributeError( + "module not available in {}-scoped context".format(self.scope) + ) return self._pyfuncitem.getparent(_pytest.python.Module).obj - @scopeproperty() - def fspath(self): - """ the file system path of the test module which collected this test. """ - return self._pyfuncitem.fspath + @property + def fspath(self) -> py.path.local: + """The file system path of the test module which collected this test.""" + if self.scope not in ("function", "class", "module", "package"): + raise AttributeError( + "module not available in {}-scoped context".format(self.scope) + ) + # TODO: Remove ignore once _pyfuncitem is properly typed. + return self._pyfuncitem.fspath # type: ignore @property def keywords(self): - """ keywords/markers dictionary for the underlying node. """ + """Keywords/markers dictionary for the underlying node.""" return self.node.keywords @property def session(self): - """ pytest session object. """ + """Pytest session object.""" return self._pyfuncitem.session - def addfinalizer(self, finalizer): - """ add finalizer/teardown function to be called after the - last test within the requesting test context finished - execution. """ - # XXX usually this method is shadowed by fixturedef specific ones + def addfinalizer(self, finalizer: Callable[[], object]) -> None: + """Add finalizer/teardown function to be called after the last test + within the requesting test context finished execution.""" + # XXX usually this method is shadowed by fixturedef specific ones. self._addfinalizer(finalizer, scope=self.scope) - def _addfinalizer(self, finalizer, scope): + def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None: colitem = self._getscopeitem(scope) self._pyfuncitem.session._setupstate.addfinalizer( finalizer=finalizer, colitem=colitem ) - def applymarker(self, marker): - """ Apply a marker to a single test function invocation. + def applymarker(self, marker) -> None: + """Apply a marker to a single test function invocation. + This method is useful if you don't want to have a keyword/marker on all function invocations. - :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object - created by a call to ``pytest.mark.NAME(...)``. + :param marker: + A :py:class:`_pytest.mark.MarkDecorator` object created by a call + to ``pytest.mark.NAME(...)``. """ self.node.add_marker(marker) - def raiseerror(self, msg): - """ raise a FixtureLookupError with the given message. """ + def raiseerror(self, msg: Optional[str]) -> "NoReturn": + """Raise a FixtureLookupError with the given message.""" raise self._fixturemanager.FixtureLookupError(None, self, msg) - def _fillfixtures(self): + def _fillfixtures(self) -> None: item = self._pyfuncitem fixturenames = getattr(item, "fixturenames", self.fixturenames) for argname in fixturenames: if argname not in item.funcargs: item.funcargs[argname] = self.getfixturevalue(argname) - def getfixturevalue(self, argname): - """ Dynamically run a named fixture function. + def getfixturevalue(self, argname: str) -> Any: + """Dynamically run a named fixture function. Declaring fixtures via function argument is recommended where possible. But if you can only decide whether to use another fixture at test setup time, you may use this function to retrieve it inside a fixture or test function body. - """ - return self._get_active_fixturedef(argname).cached_result[0] - - def getfuncargvalue(self, argname): - """ Deprecated, use getfixturevalue. """ - from _pytest import deprecated - warnings.warn(deprecated.GETFUNCARGVALUE, stacklevel=2) - return self.getfixturevalue(argname) + :raises pytest.FixtureLookupError: + If the given fixture could not be found. + """ + fixturedef = self._get_active_fixturedef(argname) + assert fixturedef.cached_result is not None + return fixturedef.cached_result[0] - def _get_active_fixturedef(self, argname): + def _get_active_fixturedef( + self, argname: str + ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: try: return self._fixture_defs[argname] except KeyError: @@ -494,33 +584,34 @@ class FixtureRequest(FuncargnamesCompatAttr): except FixtureLookupError: if argname == "request": cached_result = (self, [0], None) - scope = "function" + scope = "function" # type: _Scope return PseudoFixtureDef(cached_result, scope) raise - # remove indent to prevent the python3 exception - # from leaking into the call + # Remove indent to prevent the python3 exception + # from leaking into the call. self._compute_fixture_value(fixturedef) self._fixture_defs[argname] = fixturedef return fixturedef - def _get_fixturestack(self): + def _get_fixturestack(self) -> List["FixtureDef[Any]"]: current = self - values = [] + values = [] # type: List[FixtureDef[Any]] while 1: fixturedef = getattr(current, "_fixturedef", None) if fixturedef is None: values.reverse() return values values.append(fixturedef) + assert isinstance(current, SubRequest) current = current._parent_request - def _compute_fixture_value(self, fixturedef): - """ - Creates a SubRequest based on "self" and calls the execute method of the given fixturedef object. This will - force the FixtureDef object to throw away any previous results and compute a new fixture value, which - will be stored into the FixtureDef object itself. + def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None: + """Create a SubRequest based on "self" and call the execute method + of the given FixtureDef object. - :param FixtureDef fixturedef: + This will force the FixtureDef object to throw away any previous + results and compute a new fixture value, which will be stored into + the FixtureDef object itself. """ # prepare a subrequest object before calling fixture function # (latter managed by fixturedef) @@ -548,11 +639,13 @@ class FixtureRequest(FuncargnamesCompatAttr): if has_params: frame = inspect.stack()[3] frameinfo = inspect.getframeinfo(frame[0]) - source_path = frameinfo.filename + source_path = py.path.local(frameinfo.filename) source_lineno = frameinfo.lineno - source_path = py.path.local(source_path) - if source_path.relto(funcitem.config.rootdir): - source_path = source_path.relto(funcitem.config.rootdir) + rel_source_path = source_path.relto(funcitem.config.rootdir) + if rel_source_path: + source_path_str = rel_source_path + else: + source_path_str = str(source_path) msg = ( "The requested fixture has no parameter defined for test:\n" " {}\n\n" @@ -561,44 +654,42 @@ class FixtureRequest(FuncargnamesCompatAttr): funcitem.nodeid, fixturedef.argname, getlocation(fixturedef.func, funcitem.config.rootdir), - source_path, + source_path_str, source_lineno, ) ) fail(msg, pytrace=False) else: param_index = funcitem.callspec.indices[argname] - # if a parametrize invocation set a scope it will override - # the static scope defined with the fixture function + # If a parametrize invocation set a scope it will override + # the static scope defined with the fixture function. paramscopenum = funcitem.callspec._arg2scopenum.get(argname) if paramscopenum is not None: scope = scopes[paramscopenum] subrequest = SubRequest(self, scope, param, param_index, fixturedef) - # check if a higher-level scoped fixture accesses a lower level one + # Check if a higher-level scoped fixture accesses a lower level one. subrequest._check_scope(argname, self.scope, scope) - - # clear sys.exc_info before invoking the fixture (python bug?) - # if it's not explicitly cleared it will leak into the call - exc_clear() try: - # call the fixture function + # Call the fixture function. fixturedef.execute(request=subrequest) finally: self._schedule_finalizers(fixturedef, subrequest) - def _schedule_finalizers(self, fixturedef, subrequest): - # if fixture function failed it might have registered finalizers + def _schedule_finalizers( + self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + ) -> None: + # If fixture function failed it might have registered finalizers. self.session._setupstate.addfinalizer( functools.partial(fixturedef.finish, request=subrequest), subrequest.node ) - def _check_scope(self, argname, invoking_scope, requested_scope): + def _check_scope(self, argname, invoking_scope: "_Scope", requested_scope) -> None: if argname == "request": return if scopemismatch(invoking_scope, requested_scope): - # try to report something helpful + # Try to report something helpful. lines = self._factorytraceback() fail( "ScopeMismatch: You tried to access the %r scoped " @@ -608,7 +699,7 @@ class FixtureRequest(FuncargnamesCompatAttr): pytrace=False, ) - def _factorytraceback(self): + def _factorytraceback(self) -> List[str]: lines = [] for fixturedef in self._get_fixturestack(): factory = fixturedef.func @@ -620,29 +711,38 @@ class FixtureRequest(FuncargnamesCompatAttr): def _getscopeitem(self, scope): if scope == "function": - # this might also be a non-function Item despite its attribute name + # This might also be a non-function Item despite its attribute name. return self._pyfuncitem if scope == "package": - node = get_scope_package(self._pyfuncitem, self._fixturedef) + # FIXME: _fixturedef is not defined on FixtureRequest (this class), + # but on FixtureRequest (a subclass). + node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] else: node = get_scope_node(self._pyfuncitem, scope) if node is None and scope == "class": - # fallback to function item itself + # Fallback to function item itself. node = self._pyfuncitem assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format( scope, self._pyfuncitem ) return node - def __repr__(self): + def __repr__(self) -> str: return "<FixtureRequest for %r>" % (self.node) +@final class SubRequest(FixtureRequest): - """ a sub request for handling getting a fixture from a - test function/fixture. """ + """A sub request for handling getting a fixture from a test function/fixture.""" - def __init__(self, request, scope, param, param_index, fixturedef): + def __init__( + self, + request: "FixtureRequest", + scope: "_Scope", + param, + param_index: int, + fixturedef: "FixtureDef[object]", + ) -> None: self._parent_request = request self.fixturename = fixturedef.argname if param is not NOTSET: @@ -656,37 +756,39 @@ class SubRequest(FixtureRequest): self._arg2index = request._arg2index self._fixturemanager = request._fixturemanager - def __repr__(self): - return "<SubRequest %r for %r>" % (self.fixturename, self._pyfuncitem) + def __repr__(self) -> str: + return "<SubRequest {!r} for {!r}>".format(self.fixturename, self._pyfuncitem) - def addfinalizer(self, finalizer): + def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._fixturedef.addfinalizer(finalizer) - def _schedule_finalizers(self, fixturedef, subrequest): - # if the executing fixturedef was not explicitly requested in the argument list (via + def _schedule_finalizers( + self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest" + ) -> None: + # If the executing fixturedef was not explicitly requested in the argument list (via # getfixturevalue inside the fixture call) then ensure this fixture def will be finished - # first - if fixturedef.argname not in self.funcargnames: + # first. + if fixturedef.argname not in self.fixturenames: fixturedef.addfinalizer( functools.partial(self._fixturedef.finish, request=self) ) - super(SubRequest, self)._schedule_finalizers(fixturedef, subrequest) + super()._schedule_finalizers(fixturedef, subrequest) -scopes = "session package module class function".split() +scopes = ["session", "package", "module", "class", "function"] # type: List[_Scope] scopenum_function = scopes.index("function") -def scopemismatch(currentscope, newscope): +def scopemismatch(currentscope: "_Scope", newscope: "_Scope") -> bool: return scopes.index(newscope) > scopes.index(currentscope) -def scope2index(scope, descr, where=None): +def scope2index(scope: str, descr: str, where: Optional[str] = None) -> int: """Look up the index of ``scope`` and raise a descriptive value error - if not defined. - """ + if not defined.""" + strscopes = scopes # type: Sequence[str] try: - return scopes.index(scope) + return strscopes.index(scope) except ValueError: fail( "{} {}got an unexpected scope value '{}'".format( @@ -696,34 +798,37 @@ def scope2index(scope, descr, where=None): ) +@final class FixtureLookupError(LookupError): - """ could not return a requested Fixture (missing or invalid). """ + """Could not return a requested fixture (missing or invalid).""" - def __init__(self, argname, request, msg=None): + def __init__( + self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None + ) -> None: self.argname = argname self.request = request self.fixturestack = request._get_fixturestack() self.msg = msg - def formatrepr(self): - tblines = [] + def formatrepr(self) -> "FixtureLookupErrorRepr": + tblines = [] # type: List[str] addline = tblines.append stack = [self.request._pyfuncitem.obj] stack.extend(map(lambda x: x.func, self.fixturestack)) msg = self.msg if msg is not None: - # the last fixture raise an error, let's present - # it at the requesting side + # The last fixture raise an error, let's present + # it at the requesting side. stack = stack[:-1] for function in stack: fspath, lineno = getfslineno(function) try: lines, _ = inspect.getsourcelines(get_real_func(function)) - except (IOError, IndexError, TypeError): + except (OSError, IndexError, TypeError): error_msg = "file %s, line %s: source code not available" addline(error_msg % (fspath, lineno + 1)) else: - addline("file %s, line %s" % (fspath, lineno + 1)) + addline("file {}, line {}".format(fspath, lineno + 1)) for i, line in enumerate(lines): line = line.rstrip() addline(" " + line) @@ -751,14 +856,21 @@ class FixtureLookupError(LookupError): class FixtureLookupErrorRepr(TerminalRepr): - def __init__(self, filename, firstlineno, tblines, errorstring, argname): + def __init__( + self, + filename: Union[str, py.path.local], + firstlineno: int, + tblines: Sequence[str], + errorstring: str, + argname: Optional[str], + ) -> None: self.tblines = tblines self.errorstring = errorstring self.filename = filename self.firstlineno = firstlineno self.argname = argname - def toterminal(self, tw): + def toterminal(self, tw: TerminalWriter) -> None: # tw.line("FixtureLookupError: %s" %(self.argname), red=True) for tbline in self.tblines: tw.line(tbline.rstrip()) @@ -777,207 +889,300 @@ class FixtureLookupErrorRepr(TerminalRepr): tw.line("%s:%d" % (self.filename, self.firstlineno + 1)) -def fail_fixturefunc(fixturefunc, msg): +def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn": fs, lineno = getfslineno(fixturefunc) - location = "%s:%s" % (fs, lineno + 1) + location = "{}:{}".format(fs, lineno + 1) source = _pytest._code.Source(fixturefunc) fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False) -def call_fixture_func(fixturefunc, request, kwargs): - yieldctx = is_generator(fixturefunc) - if yieldctx: - it = fixturefunc(**kwargs) - res = next(it) - finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, it) +def call_fixture_func( + fixturefunc: "_FixtureFunc[_FixtureValue]", request: FixtureRequest, kwargs +) -> _FixtureValue: + if is_generator(fixturefunc): + fixturefunc = cast( + Callable[..., Generator[_FixtureValue, None, None]], fixturefunc + ) + generator = fixturefunc(**kwargs) + try: + fixture_result = next(generator) + except StopIteration: + raise ValueError( + "{} did not yield a value".format(request.fixturename) + ) from None + finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator) request.addfinalizer(finalizer) else: - res = fixturefunc(**kwargs) - return res + fixturefunc = cast(Callable[..., _FixtureValue], fixturefunc) + fixture_result = fixturefunc(**kwargs) + return fixture_result -def _teardown_yield_fixture(fixturefunc, it): - """Executes the teardown of a fixture function by advancing the iterator after the - yield and ensure the iteration ends (if not it means there is more than one yield in the function)""" +def _teardown_yield_fixture(fixturefunc, it) -> None: + """Execute the teardown of a fixture function by advancing the iterator + after the yield and ensure the iteration ends (if not it means there is + more than one yield in the function).""" try: next(it) except StopIteration: pass else: - fail_fixturefunc( - fixturefunc, "yield_fixture function has more than one 'yield'" + fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'") + + +def _eval_scope_callable( + scope_callable: "Callable[[str, Config], _Scope]", + fixture_name: str, + config: Config, +) -> "_Scope": + try: + # Type ignored because there is no typing mechanism to specify + # keyword arguments, currently. + result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg] + except Exception as e: + raise TypeError( + "Error evaluating {} while defining fixture '{}'.\n" + "Expected a function with the signature (*, fixture_name, config)".format( + scope_callable, fixture_name + ) + ) from e + if not isinstance(result, str): + fail( + "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n" + "{!r}".format(scope_callable, fixture_name, result), + pytrace=False, ) + return result -class FixtureDef(object): - """ A container for a factory definition. """ +@final +class FixtureDef(Generic[_FixtureValue]): + """A container for a factory definition.""" def __init__( self, - fixturemanager, + fixturemanager: "FixtureManager", baseid, - argname, - func, - scope, - params, - unittest=False, - ids=None, - ): + argname: str, + func: "_FixtureFunc[_FixtureValue]", + scope: "Union[_Scope, Callable[[str, Config], _Scope]]", + params: Optional[Sequence[object]], + unittest: bool = False, + ids: Optional[ + Union[ + Tuple[Union[None, str, float, int, bool], ...], + Callable[[Any], Optional[object]], + ] + ] = None, + ) -> None: self._fixturemanager = fixturemanager self.baseid = baseid or "" self.has_location = baseid is not None self.func = func self.argname = argname - self.scope = scope + if callable(scope): + scope_ = _eval_scope_callable(scope, argname, fixturemanager.config) + else: + scope_ = scope self.scopenum = scope2index( - scope or "function", + # TODO: Check if the `or` here is really necessary. + scope_ or "function", # type: ignore[unreachable] descr="Fixture '{}'".format(func.__name__), where=baseid, ) - self.params = params - self.argnames = getfuncargnames(func, is_method=unittest) + self.scope = scope_ + self.params = params # type: Optional[Sequence[object]] + self.argnames = getfuncargnames( + func, name=argname, is_method=unittest + ) # type: Tuple[str, ...] self.unittest = unittest self.ids = ids - self._finalizers = [] + self.cached_result = None # type: Optional[_FixtureCachedResult[_FixtureValue]] + self._finalizers = [] # type: List[Callable[[], object]] - def addfinalizer(self, finalizer): + def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._finalizers.append(finalizer) - def finish(self, request): - exceptions = [] + def finish(self, request: SubRequest) -> None: + exc = None try: while self._finalizers: try: func = self._finalizers.pop() func() - except: # noqa - exceptions.append(sys.exc_info()) - if exceptions: - e = exceptions[0] - # Ensure to not keep frame references through traceback. - del exceptions - six.reraise(*e) + except BaseException as e: + # XXX Only first exception will be seen by user, + # ideally all should be reported. + if exc is None: + exc = e + if exc: + raise exc finally: hook = self._fixturemanager.session.gethookproxy(request.node.fspath) hook.pytest_fixture_post_finalizer(fixturedef=self, request=request) - # even if finalization fails, we invalidate - # the cached fixture value and remove - # all finalizers because they may be bound methods which will - # keep instances alive - if hasattr(self, "cached_result"): - del self.cached_result + # Even if finalization fails, we invalidate the cached fixture + # value and remove all finalizers because they may be bound methods + # which will keep instances alive. + self.cached_result = None self._finalizers = [] - def execute(self, request): - # get required arguments and register our own finish() - # with their finalization + def execute(self, request: SubRequest) -> _FixtureValue: + # Get required arguments and register our own finish() + # with their finalization. for argname in self.argnames: fixturedef = request._get_active_fixturedef(argname) if argname != "request": + # PseudoFixtureDef is only for "request". + assert isinstance(fixturedef, FixtureDef) fixturedef.addfinalizer(functools.partial(self.finish, request=request)) - my_cache_key = request.param_index - cached_result = getattr(self, "cached_result", None) - if cached_result is not None: - result, cache_key, err = cached_result - if my_cache_key == cache_key: - if err is not None: - six.reraise(*err) + my_cache_key = self.cache_key(request) + if self.cached_result is not None: + # note: comparison with `==` can fail (or be expensive) for e.g. + # numpy arrays (#6497). + cache_key = self.cached_result[1] + if my_cache_key is cache_key: + if self.cached_result[2] is not None: + _, val, tb = self.cached_result[2] + raise val.with_traceback(tb) else: + result = self.cached_result[0] return result - # we have a previous but differently parametrized fixture instance - # so we need to tear it down before creating a new one + # We have a previous but differently parametrized fixture instance + # so we need to tear it down before creating a new one. self.finish(request) - assert not hasattr(self, "cached_result") + assert self.cached_result is None hook = self._fixturemanager.session.gethookproxy(request.node.fspath) - return hook.pytest_fixture_setup(fixturedef=self, request=request) + result = hook.pytest_fixture_setup(fixturedef=self, request=request) + return result + + def cache_key(self, request: SubRequest) -> object: + return request.param_index if not hasattr(request, "param") else request.param - def __repr__(self): - return "<FixtureDef argname=%r scope=%r baseid=%r>" % ( - self.argname, - self.scope, - self.baseid, + def __repr__(self) -> str: + return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format( + self.argname, self.scope, self.baseid ) -def resolve_fixture_function(fixturedef, request): - """Gets the actual callable that can be called to obtain the fixture value, dealing with unittest-specific - instances and bound methods. - """ +def resolve_fixture_function( + fixturedef: FixtureDef[_FixtureValue], request: FixtureRequest +) -> "_FixtureFunc[_FixtureValue]": + """Get the actual callable that can be called to obtain the fixture + value, dealing with unittest-specific instances and bound methods.""" fixturefunc = fixturedef.func if fixturedef.unittest: if request.instance is not None: - # bind the unbound method to the TestCase instance - fixturefunc = fixturedef.func.__get__(request.instance) + # Bind the unbound method to the TestCase instance. + fixturefunc = fixturedef.func.__get__(request.instance) # type: ignore[union-attr] else: - # the fixture function needs to be bound to the actual + # The fixture function needs to be bound to the actual # request.instance so that code working with "fixturedef" behaves # as expected. if request.instance is not None: + # Handle the case where fixture is defined not in a test class, but some other class + # (for example a plugin class with a fixture), see #2270. + if hasattr(fixturefunc, "__self__") and not isinstance( + request.instance, fixturefunc.__self__.__class__ # type: ignore[union-attr] + ): + return fixturefunc fixturefunc = getimfunc(fixturedef.func) if fixturefunc != fixturedef.func: - fixturefunc = fixturefunc.__get__(request.instance) + fixturefunc = fixturefunc.__get__(request.instance) # type: ignore[union-attr] return fixturefunc -def pytest_fixture_setup(fixturedef, request): - """ Execution of fixture setup. """ +def pytest_fixture_setup( + fixturedef: FixtureDef[_FixtureValue], request: SubRequest +) -> _FixtureValue: + """Execution of fixture setup.""" kwargs = {} for argname in fixturedef.argnames: fixdef = request._get_active_fixturedef(argname) + assert fixdef.cached_result is not None result, arg_cache_key, exc = fixdef.cached_result request._check_scope(argname, request.scope, fixdef.scope) kwargs[argname] = result fixturefunc = resolve_fixture_function(fixturedef, request) - my_cache_key = request.param_index + my_cache_key = fixturedef.cache_key(request) try: result = call_fixture_func(fixturefunc, request, kwargs) except TEST_OUTCOME: - fixturedef.cached_result = (None, my_cache_key, sys.exc_info()) + exc_info = sys.exc_info() + assert exc_info[0] is not None + fixturedef.cached_result = (None, my_cache_key, exc_info) raise fixturedef.cached_result = (result, my_cache_key, None) return result -def _ensure_immutable_ids(ids): +def _ensure_immutable_ids( + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ], +) -> Optional[ + Union[ + Tuple[Union[None, str, float, int, bool], ...], + Callable[[Any], Optional[object]], + ] +]: if ids is None: - return + return None if callable(ids): return ids return tuple(ids) +def _params_converter( + params: Optional[Iterable[object]], +) -> Optional[Tuple[object, ...]]: + return tuple(params) if params is not None else None + + def wrap_function_to_error_out_if_called_directly(function, fixture_marker): """Wrap the given fixture function so we can raise an error about it being called directly, - instead of used as an argument in a test function. - """ - message = FIXTURE_FUNCTION_CALL.format( - name=fixture_marker.name or function.__name__ - ) - - @six.wraps(function) + instead of used as an argument in a test function.""" + message = ( + 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n' + "but are created automatically when test functions request them as parameters.\n" + "See https://docs.pytest.org/en/stable/fixture.html for more information about fixtures, and\n" + "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code." + ).format(name=fixture_marker.name or function.__name__) + + @functools.wraps(function) def result(*args, **kwargs): fail(message, pytrace=False) - # keep reference to the original function in our own custom attribute so we don't unwrap - # further than this point and lose useful wrappings like @mock.patch (#3774) - result.__pytest_wrapped__ = _PytestWrapper(function) + # Keep reference to the original function in our own custom attribute so we don't unwrap + # further than this point and lose useful wrappings like @mock.patch (#3774). + result.__pytest_wrapped__ = _PytestWrapper(function) # type: ignore[attr-defined] return result +@final @attr.s(frozen=True) -class FixtureFunctionMarker(object): - scope = attr.ib() - params = attr.ib(converter=attr.converters.optional(tuple)) - autouse = attr.ib(default=False) - ids = attr.ib(default=None, converter=_ensure_immutable_ids) - name = attr.ib(default=None) - - def __call__(self, function): - if isclass(function): +class FixtureFunctionMarker: + scope = attr.ib(type="Union[_Scope, Callable[[str, Config], _Scope]]") + params = attr.ib(type=Optional[Tuple[object, ...]], converter=_params_converter) + autouse = attr.ib(type=bool, default=False) + ids = attr.ib( + type=Union[ + Tuple[Union[None, str, float, int, bool], ...], + Callable[[Any], Optional[object]], + ], + default=None, + converter=_ensure_immutable_ids, + ) + name = attr.ib(type=Optional[str], default=None) + + def __call__(self, function: _FixtureFunction) -> _FixtureFunction: + if inspect.isclass(function): raise ValueError("class fixtures not supported (maybe in the future)") if getattr(function, "_pytestfixturefunction", False): @@ -989,79 +1194,157 @@ class FixtureFunctionMarker(object): name = self.name or function.__name__ if name == "request": - warnings.warn(FIXTURE_NAMED_REQUEST) - function._pytestfixturefunction = self + location = getlocation(function) + fail( + "'request' is a reserved word for fixtures, use another name:\n {}".format( + location + ), + pytrace=False, + ) + + # Type ignored because https://github.com/python/mypy/issues/2087. + function._pytestfixturefunction = self # type: ignore[attr-defined] return function -def fixture(scope="function", params=None, autouse=False, ids=None, name=None): +@overload +def fixture( + fixture_function: _FixtureFunction, + *, + scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = ..., + params: Optional[Iterable[object]] = ..., + autouse: bool = ..., + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = ..., + name: Optional[str] = ... +) -> _FixtureFunction: + ... + + +@overload # noqa: F811 +def fixture( # noqa: F811 + fixture_function: None = ..., + *, + scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = ..., + params: Optional[Iterable[object]] = ..., + autouse: bool = ..., + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = ..., + name: Optional[str] = None +) -> FixtureFunctionMarker: + ... + + +def fixture( # noqa: F811 + fixture_function: Optional[_FixtureFunction] = None, + *, + scope: "Union[_Scope, Callable[[str, Config], _Scope]]" = "function", + params: Optional[Iterable[object]] = None, + autouse: bool = False, + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = None, + name: Optional[str] = None +) -> Union[FixtureFunctionMarker, _FixtureFunction]: """Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a fixture function. The name of the fixture function can later be referenced to cause its - invocation ahead of running tests: test - modules or classes can use the ``pytest.mark.usefixtures(fixturename)`` - marker. - - Test functions can directly use fixture names as input - arguments in which case the fixture instance returned from the fixture - function will be injected. - - Fixtures can provide their values to test functions using ``return`` or ``yield`` - statements. When using ``yield`` the code block after the ``yield`` statement is executed - as teardown code regardless of the test outcome, and must yield exactly once. - - :arg scope: the scope for which this fixture is shared, one of - ``"function"`` (default), ``"class"``, ``"module"``, - ``"package"`` or ``"session"``. - - ``"package"`` is considered **experimental** at this time. - - :arg params: an optional list of parameters which will cause multiple - invocations of the fixture function and all of the tests - using it. - The current parameter is available in ``request.param``. - - :arg autouse: if True, the fixture func is activated for all tests that - can see it. If False (the default) then an explicit - reference is needed to activate the fixture. - - :arg ids: list of string ids each corresponding to the params - so that they are part of the test id. If no ids are provided - they will be generated automatically from the params. - - :arg name: the name of the fixture. This defaults to the name of the - decorated function. If a fixture is used in the same module in - which it is defined, the function name of the fixture will be - shadowed by the function arg that requests the fixture; one way - to resolve this is to name the decorated function - ``fixture_<fixturename>`` and then use - ``@pytest.fixture(name='<fixturename>')``. + invocation ahead of running tests: test modules or classes can use the + ``pytest.mark.usefixtures(fixturename)`` marker. + + Test functions can directly use fixture names as input arguments in which + case the fixture instance returned from the fixture function will be + injected. + + Fixtures can provide their values to test functions using ``return`` or + ``yield`` statements. When using ``yield`` the code block after the + ``yield`` statement is executed as teardown code regardless of the test + outcome, and must yield exactly once. + + :param scope: + The scope for which this fixture is shared; one of ``"function"`` + (default), ``"class"``, ``"module"``, ``"package"`` or ``"session"``. + + This parameter may also be a callable which receives ``(fixture_name, config)`` + as parameters, and must return a ``str`` with one of the values mentioned above. + + See :ref:`dynamic scope` in the docs for more information. + + :param params: + An optional list of parameters which will cause multiple invocations + of the fixture function and all of the tests using it. The current + parameter is available in ``request.param``. + + :param autouse: + If True, the fixture func is activated for all tests that can see it. + If False (the default), an explicit reference is needed to activate + the fixture. + + :param ids: + List of string ids each corresponding to the params so that they are + part of the test id. If no ids are provided they will be generated + automatically from the params. + + :param name: + The name of the fixture. This defaults to the name of the decorated + function. If a fixture is used in the same module in which it is + defined, the function name of the fixture will be shadowed by the + function arg that requests the fixture; one way to resolve this is to + name the decorated function ``fixture_<fixturename>`` and then use + ``@pytest.fixture(name='<fixturename>')``. """ - if callable(scope) and params is None and autouse is False: - # direct decoration - return FixtureFunctionMarker("function", params, autouse, name=name)(scope) - if params is not None and not isinstance(params, (list, tuple)): - params = list(params) - return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) + fixture_marker = FixtureFunctionMarker( + scope=scope, params=params, autouse=autouse, ids=ids, name=name, + ) + # Direct decoration. + if fixture_function: + return fixture_marker(fixture_function) -def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None): - """ (return a) decorator to mark a yield-fixture factory function. + return fixture_marker + + +def yield_fixture( + fixture_function=None, + *args, + scope="function", + params=None, + autouse=False, + ids=None, + name=None +): + """(Return a) decorator to mark a yield-fixture factory function. .. deprecated:: 3.0 Use :py:func:`pytest.fixture` directly instead. """ - return fixture(scope=scope, params=params, autouse=autouse, ids=ids, name=name) - - -defaultfuncargprefixmarker = fixture() + return fixture( + fixture_function, + *args, + scope=scope, + params=params, + autouse=autouse, + ids=ids, + name=name, + ) @fixture(scope="session") -def pytestconfig(request): +def pytestconfig(request: FixtureRequest) -> Config: """Session-scoped fixture that returns the :class:`_pytest.config.Config` object. Example:: @@ -1074,7 +1357,7 @@ def pytestconfig(request): return request.config -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addini( "usefixtures", type="args", @@ -1083,9 +1366,8 @@ def pytest_addoption(parser): ) -class FixtureManager(object): - """ - pytest fixtures definitions and information is stored and managed +class FixtureManager: + """pytest fixture definitions and information is stored and managed from this class. During collection fm.parsefactories() is called multiple times to parse @@ -1098,7 +1380,7 @@ class FixtureManager(object): which themselves offer a fixturenames attribute. The FuncFixtureInfo object holds information about fixtures and FixtureDefs - relevant for a particular function. An initial list of fixtures is + relevant for a particular function. An initial list of fixtures is assembled like this: - ini-defined usefixtures @@ -1108,7 +1390,7 @@ class FixtureManager(object): Subsequently the funcfixtureinfo.fixturenames attribute is computed as the closure of the fixtures needed to setup the initial fixtures, - i. e. fixtures needed by fixture functions themselves are appended + i.e. fixtures needed by fixture functions themselves are appended to the fixturenames list. Upon the test-setup phases all fixturenames are instantiated, retrieved @@ -1118,27 +1400,26 @@ class FixtureManager(object): FixtureLookupError = FixtureLookupError FixtureLookupErrorRepr = FixtureLookupErrorRepr - def __init__(self, session): + def __init__(self, session: "Session") -> None: self.session = session - self.config = session.config - self._arg2fixturedefs = {} - self._holderobjseen = set() - self._arg2finish = {} - self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] + self.config = session.config # type: Config + self._arg2fixturedefs = {} # type: Dict[str, List[FixtureDef[Any]]] + self._holderobjseen = set() # type: Set[object] + self._nodeid_and_autousenames = [ + ("", self.config.getini("usefixtures")) + ] # type: List[Tuple[str, List[str]]] session.config.pluginmanager.register(self, "funcmanage") - def _get_direct_parametrize_args(self, node): - """This function returns all the direct parametrization - arguments of a node, so we don't mistake them for fixtures + def _get_direct_parametrize_args(self, node: "nodes.Node") -> List[str]: + """Return all direct parametrization arguments of a node, so we don't + mistake them for fixtures. - Check https://github.com/pytest-dev/pytest/issues/5036 + Check https://github.com/pytest-dev/pytest/issues/5036. - This things are done later as well when dealing with parametrization - so this could be improved + These things are done later as well when dealing with parametrization + so this could be improved. """ - from _pytest.mark import ParameterSet - - parametrize_argnames = [] + parametrize_argnames = [] # type: List[str] for marker in node.iter_markers(name="parametrize"): if not marker.kwargs.get("indirect", False): p_argnames, _ = ParameterSet._parse_parametrize_args( @@ -1148,42 +1429,51 @@ class FixtureManager(object): return parametrize_argnames - def getfixtureinfo(self, node, func, cls, funcargs=True): + def getfixtureinfo( + self, node: "nodes.Node", func, cls, funcargs: bool = True + ) -> FuncFixtureInfo: if funcargs and not getattr(node, "nofuncargs", False): - argnames = getfuncargnames(func, cls=cls) + argnames = getfuncargnames(func, name=node.name, cls=cls) else: argnames = () - usefixtures = itertools.chain.from_iterable( - mark.args for mark in node.iter_markers(name="usefixtures") + usefixtures = tuple( + arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args ) - initialnames = tuple(usefixtures) + argnames + initialnames = usefixtures + argnames fm = node.session._fixturemanager initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure( initialnames, node, ignore_args=self._get_direct_parametrize_args(node) ) return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs) - def pytest_plugin_registered(self, plugin): + def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: nodeid = None try: - p = py.path.local(plugin.__file__).realpath() + p = absolutepath(plugin.__file__) # type: ignore[attr-defined] except AttributeError: pass else: - # construct the base nodeid which is later used to check + from _pytest import nodes + + # Construct the base nodeid which is later used to check # what fixtures are visible for particular tests (as denoted - # by their test id) - if p.basename.startswith("conftest.py"): - nodeid = p.dirpath().relto(self.config.rootdir) - if p.sep != nodes.SEP: - nodeid = nodeid.replace(p.sep, nodes.SEP) + # by their test id). + if p.name.startswith("conftest.py"): + try: + nodeid = str(p.parent.relative_to(self.config.rootpath)) + except ValueError: + nodeid = "" + if nodeid == ".": + nodeid = "" + if os.sep != nodes.SEP: + nodeid = nodeid.replace(os.sep, nodes.SEP) self.parsefactories(plugin, nodeid) - def _getautousenames(self, nodeid): - """ return a tuple of fixture names to be used. """ - autousenames = [] + def _getautousenames(self, nodeid: str) -> List[str]: + """Return a list of fixture names to be used.""" + autousenames = [] # type: List[str] for baseid, basenames in self._nodeid_and_autousenames: if nodeid.startswith(baseid): if baseid: @@ -1194,30 +1484,32 @@ class FixtureManager(object): autousenames.extend(basenames) return autousenames - def getfixtureclosure(self, fixturenames, parentnode, ignore_args=()): - # collect the closure of all fixtures , starting with the given + def getfixtureclosure( + self, fixturenames: Tuple[str, ...], parentnode, ignore_args: Sequence[str] = () + ) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]: + # Collect the closure of all fixtures, starting with the given # fixturenames as the initial set. As we have to visit all # factory definitions anyway, we also return an arg2fixturedefs # mapping so that the caller can reuse it and does not have # to re-discover fixturedefs again for each fixturename - # (discovering matching fixtures for a given name/node is expensive) + # (discovering matching fixtures for a given name/node is expensive). parentid = parentnode.nodeid fixturenames_closure = self._getautousenames(parentid) - def merge(otherlist): + def merge(otherlist: Iterable[str]) -> None: for arg in otherlist: if arg not in fixturenames_closure: fixturenames_closure.append(arg) merge(fixturenames) - # at this point, fixturenames_closure contains what we call "initialnames", + # At this point, fixturenames_closure contains what we call "initialnames", # which is a set of fixturenames the function immediately requests. We # need to return it as well, so save this. initialnames = tuple(fixturenames_closure) - arg2fixturedefs = {} + arg2fixturedefs = {} # type: Dict[str, Sequence[FixtureDef[Any]]] lastlen = -1 while lastlen != len(fixturenames_closure): lastlen = len(fixturenames_closure) @@ -1231,7 +1523,7 @@ class FixtureManager(object): arg2fixturedefs[argname] = fixturedefs merge(fixturedefs[-1].argnames) - def sort_by_scope(arg_name): + def sort_by_scope(arg_name: str) -> int: try: fixturedefs = arg2fixturedefs[arg_name] except KeyError: @@ -1242,41 +1534,58 @@ class FixtureManager(object): fixturenames_closure.sort(key=sort_by_scope) return initialnames, fixturenames_closure, arg2fixturedefs - def pytest_generate_tests(self, metafunc): + def pytest_generate_tests(self, metafunc: "Metafunc") -> None: + """Generate new tests based on parametrized fixtures used by the given metafunc""" + + def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: + args, _ = ParameterSet._parse_parametrize_args(*mark.args, **mark.kwargs) + return args + for argname in metafunc.fixturenames: - faclist = metafunc._arg2fixturedefs.get(argname) - if faclist: - fixturedef = faclist[-1] + # Get the FixtureDefs for the argname. + fixture_defs = metafunc._arg2fixturedefs.get(argname) + if not fixture_defs: + # Will raise FixtureLookupError at setup time if not parametrized somewhere + # else (e.g @pytest.mark.parametrize) + continue + + # If the test itself parametrizes using this argname, give it + # precedence. + if any( + argname in get_parametrize_mark_argnames(mark) + for mark in metafunc.definition.iter_markers("parametrize") + ): + continue + + # In the common case we only look at the fixture def with the + # closest scope (last in the list). But if the fixture overrides + # another fixture, while requesting the super fixture, keep going + # in case the super fixture is parametrized (#1953). + for fixturedef in reversed(fixture_defs): + # Fixture is parametrized, apply it and stop. if fixturedef.params is not None: - markers = list(metafunc.definition.iter_markers("parametrize")) - for parametrize_mark in markers: - if "argnames" in parametrize_mark.kwargs: - argnames = parametrize_mark.kwargs["argnames"] - else: - argnames = parametrize_mark.args[0] - - if not isinstance(argnames, (tuple, list)): - argnames = [ - x.strip() for x in argnames.split(",") if x.strip() - ] - if argname in argnames: - break - else: - metafunc.parametrize( - argname, - fixturedef.params, - indirect=True, - scope=fixturedef.scope, - ids=fixturedef.ids, - ) - else: - continue # will raise FixtureLookupError at setup time + metafunc.parametrize( + argname, + fixturedef.params, + indirect=True, + scope=fixturedef.scope, + ids=fixturedef.ids, + ) + break - def pytest_collection_modifyitems(self, items): - # separate parametrized setups + # Not requesting the overridden super fixture, stop. + if argname not in fixturedef.argnames: + break + + # Try next super fixture, if any. + + def pytest_collection_modifyitems(self, items: "List[nodes.Item]") -> None: + # Separate parametrized setups. items[:] = reorder_items(items) - def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): + def parsefactories( + self, node_or_obj, nodeid=NOTSET, unittest: bool = False + ) -> None: if nodeid is not NOTSET: holderobj = node_or_obj else: @@ -1293,29 +1602,26 @@ class FixtureManager(object): obj = safe_getattr(holderobj, name, None) marker = getfixturemarker(obj) if not isinstance(marker, FixtureFunctionMarker): - # magic globals with __getattr__ might have got us a wrong - # fixture attribute + # Magic globals with __getattr__ might have got us a wrong + # fixture attribute. continue if marker.name: name = marker.name - # during fixture definition we wrap the original fixture function - # to issue a warning if called directly, so here we unwrap it in order to not emit the warning - # when pytest itself calls the fixture function - if six.PY2 and unittest: - # hack on Python 2 because of the unbound methods - obj = get_real_func(obj) - else: - obj = get_real_method(obj, holderobj) + # During fixture definition we wrap the original fixture function + # to issue a warning if called directly, so here we unwrap it in + # order to not emit the warning when pytest itself calls the + # fixture function. + obj = get_real_method(obj, holderobj) fixture_def = FixtureDef( - self, - nodeid, - name, - obj, - marker.scope, - marker.params, + fixturemanager=self, + baseid=nodeid, + argname=name, + func=obj, + scope=marker.scope, + params=marker.params, unittest=unittest, ids=marker.ids, ) @@ -1336,13 +1642,14 @@ class FixtureManager(object): if autousenames: self._nodeid_and_autousenames.append((nodeid or "", autousenames)) - def getfixturedefs(self, argname, nodeid): - """ - Gets a list of fixtures which are applicable to the given node id. + def getfixturedefs( + self, argname: str, nodeid: str + ) -> Optional[Sequence[FixtureDef[Any]]]: + """Get a list of fixtures which are applicable to the given node id. - :param str argname: name of the fixture to search for - :param str nodeid: full node id of the requesting test. - :return: list[FixtureDef] + :param str argname: Name of the fixture to search for. + :param str nodeid: Full node id of the requesting test. + :rtype: Sequence[FixtureDef] """ try: fixturedefs = self._arg2fixturedefs[argname] @@ -1350,7 +1657,11 @@ class FixtureManager(object): return None return tuple(self._matchfactories(fixturedefs, nodeid)) - def _matchfactories(self, fixturedefs, nodeid): + def _matchfactories( + self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str + ) -> Iterator[FixtureDef[Any]]: + from _pytest import nodes + for fixturedef in fixturedefs: if nodes.ischildnode(fixturedef.baseid, nodeid): yield fixturedef diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py index aeeec2a56b4..8b93ed5f7f8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/freeze_support.py @@ -1,18 +1,14 @@ -# -*- coding: utf-8 -*- -""" -Provides a function to report all internal modules for using freezing tools -pytest -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +"""Provides a function to report all internal modules for using freezing +tools.""" +import types +from typing import Iterator +from typing import List +from typing import Union -def freeze_includes(): - """ - Returns a list of module names used by pytest that should be - included by cx_freeze. - """ +def freeze_includes() -> List[str]: + """Return a list of module names used by pytest that should be + included by cx_freeze.""" import py import _pytest @@ -21,25 +17,26 @@ def freeze_includes(): return result -def _iter_all_modules(package, prefix=""): - """ - Iterates over the names of all modules that can be found in the given +def _iter_all_modules( + package: Union[str, types.ModuleType], prefix: str = "", +) -> Iterator[str]: + """Iterate over the names of all modules that can be found in the given package, recursively. - Example: - _iter_all_modules(_pytest) -> - ['_pytest.assertion.newinterpret', - '_pytest.capture', - '_pytest.core', - ... - ] + + >>> import _pytest + >>> list(_iter_all_modules(_pytest)) + ['_pytest._argcomplete', '_pytest._code.code', ...] """ import os import pkgutil - if type(package) is not str: - path, prefix = package.__path__[0], package.__name__ + "." - else: + if isinstance(package, str): path = package + else: + # Type ignored because typeshed doesn't define ModuleType.__path__ + # (only defined on packages). + package_path = package.__path__ # type: ignore[attr-defined] + path, prefix = package_path[0], package.__name__ + "." for _, name, is_package in pkgutil.iter_modules([path]): if is_package: for m in _iter_all_modules(os.path.join(path, name), prefix=name + "."): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py index 56811601608..348a65edec6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/helpconfig.py @@ -1,22 +1,24 @@ -# -*- coding: utf-8 -*- -""" version info, help messages, tracing configuration. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Version info, help messages, tracing configuration.""" import os import sys from argparse import Action +from typing import List +from typing import Optional +from typing import Union import py import pytest +from _pytest.config import Config +from _pytest.config import ExitCode from _pytest.config import PrintHelp +from _pytest.config.argparsing import Parser class HelpAction(Action): - """This is an argparse Action that will raise an exception in - order to skip the rest of the argument parsing when --help is passed. + """An argparse Action that will raise an exception in order to skip the + rest of the argument parsing when --help is passed. + This prevents argparse from quitting due to missing required arguments when any are defined, for example by ``pytest_addoption``. This is similar to the way that the builtin argparse --help option is @@ -24,7 +26,7 @@ class HelpAction(Action): """ def __init__(self, option_strings, dest=None, default=False, help=None): - super(HelpAction, self).__init__( + super().__init__( option_strings=option_strings, dest=dest, const=True, @@ -36,17 +38,21 @@ class HelpAction(Action): def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) - # We should only skip the rest of the parsing after preparse is done + # We should only skip the rest of the parsing after preparse is done. if getattr(parser._parser, "after_preparse", False): raise PrintHelp -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--version", - action="store_true", - help="display pytest lib version and import information.", + "-V", + action="count", + default=0, + dest="version", + help="display pytest version and information about plugins." + "When given twice, also display information about plugins.", ) group._addoption( "-h", @@ -61,7 +67,7 @@ def pytest_addoption(parser): dest="plugins", default=[], metavar="name", - help="early-load given plugin module name or entry point (multi-allowed). " + help="early-load given plugin module name or entry point (multi-allowed).\n" "To avoid loading of plugins, use the `no:` prefix, e.g. " "`no:doctest`.", ) @@ -71,7 +77,7 @@ def pytest_addoption(parser): action="store_true", default=False, help="trace considerations of conftest.py files.", - ), + ) group.addoption( "--debug", action="store_true", @@ -91,7 +97,7 @@ def pytest_addoption(parser): @pytest.hookimpl(hookwrapper=True) def pytest_cmdline_parse(): outcome = yield - config = outcome.get_result() + config = outcome.get_result() # type: Config if config.option.debug: path = os.path.abspath("pytestdebug.log") debugfile = open(path, "w") @@ -103,14 +109,14 @@ def pytest_cmdline_parse(): py.__version__, ".".join(map(str, sys.version_info)), os.getcwd(), - config._origargs, + config.invocation_params.args, ) ) config.trace.root.setwriter(debugfile.write) undo_tracing = config.pluginmanager.enable_tracing() sys.stderr.write("writing pytestdebug information to %s\n" % path) - def unset_tracing(): + def unset_tracing() -> None: debugfile.close() sys.stderr.write("wrote pytestdebug information to %s\n" % debugfile.name) config.trace.root.setwriter(None) @@ -119,19 +125,23 @@ def pytest_cmdline_parse(): config.add_cleanup(unset_tracing) -def showversion(config): - p = py.path.local(pytest.__file__) - sys.stderr.write( - "This is pytest version %s, imported from %s\n" % (pytest.__version__, p) - ) - plugininfo = getpluginversioninfo(config) - if plugininfo: - for line in plugininfo: - sys.stderr.write(line + "\n") +def showversion(config: Config) -> None: + if config.option.version > 1: + sys.stderr.write( + "This is pytest version {}, imported from {}\n".format( + pytest.__version__, pytest.__file__ + ) + ) + plugininfo = getpluginversioninfo(config) + if plugininfo: + for line in plugininfo: + sys.stderr.write(line + "\n") + else: + sys.stderr.write("pytest {}\n".format(pytest.__version__)) -def pytest_cmdline_main(config): - if config.option.version: +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: + if config.option.version > 0: showversion(config) return 0 elif config.option.help: @@ -139,9 +149,10 @@ def pytest_cmdline_main(config): showhelp(config) config._ensure_unconfigure() return 0 + return None -def showhelp(config): +def showhelp(config: Config) -> None: import textwrap reporter = config.pluginmanager.get_plugin("terminalreporter") @@ -160,7 +171,9 @@ def showhelp(config): help, type, default = config._parser._inidict[name] if type is None: type = "string" - spec = "%s (%s):" % (name, type) + if help is None: + raise TypeError("help argument cannot be None for {}".format(name)) + spec = "{} ({}):".format(name, type) tw.write(" %s" % spec) spec_len = len(spec) if spec_len > (indent_len - 3): @@ -181,9 +194,10 @@ def showhelp(config): tw.write(" " * (indent_len - spec_len - 2)) wrapped = textwrap.wrap(help, columns - indent_len, break_on_hyphens=False) - tw.line(wrapped[0]) - for line in wrapped[1:]: - tw.line(indent + line) + if wrapped: + tw.line(wrapped[0]) + for line in wrapped[1:]: + tw.line(indent + line) tw.line() tw.line("environment variables:") @@ -194,7 +208,7 @@ def showhelp(config): ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"), ] for name, help in vars: - tw.line(" %-24s %s" % (name, help)) + tw.line(" {:<24} {}".format(name, help)) tw.line() tw.line() @@ -214,22 +228,24 @@ def showhelp(config): conftest_options = [("pytest_plugins", "list of plugin names to load")] -def getpluginversioninfo(config): +def getpluginversioninfo(config: Config) -> List[str]: lines = [] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: lines.append("setuptools registered plugins:") for plugin, dist in plugininfo: loc = getattr(plugin, "__file__", repr(plugin)) - content = "%s-%s at %s" % (dist.project_name, dist.version, loc) + content = "{}-{} at {}".format(dist.project_name, dist.version, loc) lines.append(" " + content) return lines -def pytest_report_header(config): +def pytest_report_header(config: Config) -> List[str]: lines = [] if config.option.debug or config.option.traceconfig: - lines.append("using: pytest-%s pylib-%s" % (pytest.__version__, py.__version__)) + lines.append( + "using: pytest-{} pylib-{}".format(pytest.__version__, py.__version__) + ) verinfo = getpluginversioninfo(config) if verinfo: @@ -243,5 +259,5 @@ def pytest_report_header(config): r = plugin.__file__ else: r = repr(plugin) - lines.append(" %-20s: %s" % (name, r)) + lines.append(" {:<20}: {}".format(name, r)) return lines diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py index 7ab6154b176..2f0a04a06a7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/hookspec.py @@ -1,8 +1,47 @@ -# -*- coding: utf-8 -*- -""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ +"""Hook specifications for pytest plugins which are invoked by pytest itself +and by builtin plugins.""" +from typing import Any +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Union + +import py.path from pluggy import HookspecMarker -from _pytest.deprecated import PYTEST_LOGWARNING +from _pytest.compat import TYPE_CHECKING +from _pytest.deprecated import WARNING_CAPTURED_HOOK + +if TYPE_CHECKING: + import pdb + import warnings + from typing_extensions import Literal + + from _pytest._code.code import ExceptionRepr + from _pytest.code import ExceptionInfo + from _pytest.config import Config + from _pytest.config import ExitCode + from _pytest.config import PytestPluginManager + from _pytest.config import _PluggyPlugin + from _pytest.config.argparsing import Parser + from _pytest.fixtures import FixtureDef + from _pytest.fixtures import SubRequest + from _pytest.main import Session + from _pytest.nodes import Collector + from _pytest.nodes import Item + from _pytest.outcomes import Exit + from _pytest.python import Function + from _pytest.python import Metafunc + from _pytest.python import Module + from _pytest.python import PyCollector + from _pytest.reports import CollectReport + from _pytest.reports import TestReport + from _pytest.runner import CallInfo + from _pytest.terminal import TerminalReporter + hookspec = HookspecMarker("pytest") @@ -12,12 +51,11 @@ hookspec = HookspecMarker("pytest") @hookspec(historic=True) -def pytest_addhooks(pluginmanager): - """called at plugin registration time to allow adding new hooks via a call to +def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: + """Called at plugin registration time to allow adding new hooks via a call to ``pluginmanager.add_hookspecs(module_or_class, prefix)``. - - :param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager + :param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager. .. note:: This hook is incompatible with ``hookwrapper=True``. @@ -25,11 +63,13 @@ def pytest_addhooks(pluginmanager): @hookspec(historic=True) -def pytest_plugin_registered(plugin, manager): - """ a new pytest plugin got registered. +def pytest_plugin_registered( + plugin: "_PluggyPlugin", manager: "PytestPluginManager" +) -> None: + """A new pytest plugin got registered. - :param plugin: the plugin module or instance - :param _pytest.config.PytestPluginManager manager: pytest plugin manager + :param plugin: The plugin module or instance. + :param _pytest.config.PytestPluginManager manager: pytest plugin manager. .. note:: This hook is incompatible with ``hookwrapper=True``. @@ -37,8 +77,8 @@ def pytest_plugin_registered(plugin, manager): @hookspec(historic=True) -def pytest_addoption(parser): - """register argparse-style options and ini-style config values, +def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None: + """Register argparse-style options and ini-style config values, called once at the beginning of a test run. .. note:: @@ -47,10 +87,16 @@ def pytest_addoption(parser): files situated at the tests root directory due to how pytest :ref:`discovers plugins during startup <pluginorder>`. - :arg _pytest.config.Parser parser: To add command line options, call - :py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`. + :param _pytest.config.argparsing.Parser parser: + To add command line options, call + :py:func:`parser.addoption(...) <_pytest.config.argparsing.Parser.addoption>`. To add ini-file values call :py:func:`parser.addini(...) - <_pytest.config.Parser.addini>`. + <_pytest.config.argparsing.Parser.addini>`. + + :param _pytest.config.PytestPluginManager pluginmanager: + pytest plugin manager, which can be used to install :py:func:`hookspec`'s + or :py:func:`hookimpl`'s and allow one plugin to call another plugin's hooks + to change how command line options are added. Options can later be accessed through the :py:class:`config <_pytest.config.Config>` object, respectively: @@ -70,9 +116,8 @@ def pytest_addoption(parser): @hookspec(historic=True) -def pytest_configure(config): - """ - Allows plugins and conftest files to perform initial configuration. +def pytest_configure(config: "Config") -> None: + """Allow plugins and conftest files to perform initial configuration. This hook is called for every plugin and initial conftest file after command line options have been parsed. @@ -83,7 +128,7 @@ def pytest_configure(config): .. note:: This hook is incompatible with ``hookwrapper=True``. - :arg _pytest.config.Config config: pytest config object + :param _pytest.config.Config config: The pytest config object. """ @@ -94,21 +139,24 @@ def pytest_configure(config): @hookspec(firstresult=True) -def pytest_cmdline_parse(pluginmanager, args): - """return initialized config object, parsing the specified args. +def pytest_cmdline_parse( + pluginmanager: "PytestPluginManager", args: List[str] +) -> Optional["Config"]: + """Return an initialized config object, parsing the specified args. - Stops at first non-None result, see :ref:`firstresult` + Stops at first non-None result, see :ref:`firstresult`. .. note:: - This hook will only be called for plugin classes passed to the ``plugins`` arg when using `pytest.main`_ to - perform an in-process test run. + This hook will only be called for plugin classes passed to the + ``plugins`` arg when using `pytest.main`_ to perform an in-process + test run. - :param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager - :param list[str] args: list of arguments passed on the command line + :param _pytest.config.PytestPluginManager pluginmanager: Pytest plugin manager. + :param List[str] args: List of arguments passed on the command line. """ -def pytest_cmdline_preparse(config, args): +def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None: """(**Deprecated**) modify command line arguments before option parsing. This hook is considered deprecated and will be removed in a future pytest version. Consider @@ -117,35 +165,37 @@ def pytest_cmdline_preparse(config, args): .. note:: This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - :param _pytest.config.Config config: pytest config object - :param list[str] args: list of arguments passed on the command line + :param _pytest.config.Config config: The pytest config object. + :param List[str] args: Arguments passed on the command line. """ @hookspec(firstresult=True) -def pytest_cmdline_main(config): - """ called for performing the main command line action. The default +def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: + """Called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop. .. note:: This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - Stops at first non-None result, see :ref:`firstresult` + Stops at first non-None result, see :ref:`firstresult`. - :param _pytest.config.Config config: pytest config object + :param _pytest.config.Config config: The pytest config object. """ -def pytest_load_initial_conftests(early_config, parser, args): - """ implements the loading of initial conftest files ahead +def pytest_load_initial_conftests( + early_config: "Config", parser: "Parser", args: List[str] +) -> None: + """Called to implement the loading of initial conftest files ahead of command line option parsing. .. note:: This hook will not be called for ``conftest.py`` files, only for setuptools plugins. - :param _pytest.config.Config early_config: pytest config object - :param list[str] args: list of arguments passed on the command line - :param _pytest.config.Parser parser: to add command line options + :param _pytest.config.Config early_config: The pytest config object. + :param List[str] args: Arguments passed on the command line. + :param _pytest.config.argparsing.Parser parser: To add command line options. """ @@ -155,87 +205,114 @@ def pytest_load_initial_conftests(early_config, parser, args): @hookspec(firstresult=True) -def pytest_collection(session): - """Perform the collection protocol for the given session. +def pytest_collection(session: "Session") -> Optional[object]: + """Perform the collection phase for the given session. Stops at first non-None result, see :ref:`firstresult`. + The return value is not used, but only stops further processing. + + The default collection phase is this (see individual hooks for full details): + + 1. Starting from ``session`` as the initial collector: + + 1. ``pytest_collectstart(collector)`` + 2. ``report = pytest_make_collect_report(collector)`` + 3. ``pytest_exception_interact(collector, call, report)`` if an interactive exception occurred + 4. For each collected node: + + 1. If an item, ``pytest_itemcollected(item)`` + 2. If a collector, recurse into it. + + 5. ``pytest_collectreport(report)`` + + 2. ``pytest_collection_modifyitems(session, config, items)`` + + 1. ``pytest_deselected(items)`` for any deselected items (may be called multiple times) + + 3. ``pytest_collection_finish(session)`` + 4. Set ``session.items`` to the list of collected items + 5. Set ``session.testscollected`` to the number of collected items + + You can implement this hook to only perform some action before collection, + for example the terminal plugin uses it to start displaying the collection + counter (and returns `None`). - :param _pytest.main.Session session: the pytest session object + :param pytest.Session session: The pytest session object. """ -def pytest_collection_modifyitems(session, config, items): - """ called after collection has been performed, may filter or re-order +def pytest_collection_modifyitems( + session: "Session", config: "Config", items: List["Item"] +) -> None: + """Called after collection has been performed. May filter or re-order the items in-place. - :param _pytest.main.Session session: the pytest session object - :param _pytest.config.Config config: pytest config object - :param List[_pytest.nodes.Item] items: list of item objects + :param pytest.Session session: The pytest session object. + :param _pytest.config.Config config: The pytest config object. + :param List[pytest.Item] items: List of item objects. """ -def pytest_collection_finish(session): - """ called after collection has been performed and modified. +def pytest_collection_finish(session: "Session") -> None: + """Called after collection has been performed and modified. - :param _pytest.main.Session session: the pytest session object + :param pytest.Session session: The pytest session object. """ @hookspec(firstresult=True) -def pytest_ignore_collect(path, config): - """ return True to prevent considering this path for collection. +def pytest_ignore_collect(path: py.path.local, config: "Config") -> Optional[bool]: + """Return True to prevent considering this path for collection. + This hook is consulted for all files and directories prior to calling more specific hooks. - Stops at first non-None result, see :ref:`firstresult` + Stops at first non-None result, see :ref:`firstresult`. - :param path: a :py:class:`py.path.local` - the path to analyze - :param _pytest.config.Config config: pytest config object + :param py.path.local path: The path to analyze. + :param _pytest.config.Config config: The pytest config object. """ -@hookspec(firstresult=True) -def pytest_collect_directory(path, parent): - """ called before traversing a directory for collection files. - - Stops at first non-None result, see :ref:`firstresult` - - :param path: a :py:class:`py.path.local` - the path to analyze - """ - +def pytest_collect_file( + path: py.path.local, parent: "Collector" +) -> "Optional[Collector]": + """Create a Collector for the given path, or None if not relevant. -def pytest_collect_file(path, parent): - """ return collection Node or None for the given path. Any new node - needs to have the specified ``parent`` as a parent. + The new node needs to have the specified ``parent`` as a parent. - :param path: a :py:class:`py.path.local` - the path to collect + :param py.path.local path: The path to collect. """ # logging hooks for collection -def pytest_collectstart(collector): - """ collector starts collecting. """ +def pytest_collectstart(collector: "Collector") -> None: + """Collector starts collecting.""" -def pytest_itemcollected(item): - """ we just collected a test item. """ +def pytest_itemcollected(item: "Item") -> None: + """We just collected a test item.""" -def pytest_collectreport(report): - """ collector finished collecting. """ +def pytest_collectreport(report: "CollectReport") -> None: + """Collector finished collecting.""" -def pytest_deselected(items): - """ called for test items deselected, e.g. by keyword. """ +def pytest_deselected(items: Sequence["Item"]) -> None: + """Called for deselected test items, e.g. by keyword. + + May be called multiple times. + """ @hookspec(firstresult=True) -def pytest_make_collect_report(collector): - """ perform ``collector.collect()`` and return a CollectReport. +def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectReport]": + """Perform ``collector.collect()`` and return a CollectReport. - Stops at first non-None result, see :ref:`firstresult` """ + Stops at first non-None result, see :ref:`firstresult`. + """ # ------------------------------------------------------------------------- @@ -244,175 +321,216 @@ def pytest_make_collect_report(collector): @hookspec(firstresult=True) -def pytest_pycollect_makemodule(path, parent): - """ return a Module collector or None for the given path. +def pytest_pycollect_makemodule(path: py.path.local, parent) -> Optional["Module"]: + """Return a Module collector or None for the given path. + This hook will be called for each matching test module path. The pytest_collect_file hook needs to be used if you want to create test modules for files that do not match as a test module. - Stops at first non-None result, see :ref:`firstresult` + Stops at first non-None result, see :ref:`firstresult`. - :param path: a :py:class:`py.path.local` - the path of module to collect + :param py.path.local path: The path of module to collect. """ @hookspec(firstresult=True) -def pytest_pycollect_makeitem(collector, name, obj): - """ return custom item/collector for a python object in a module, or None. +def pytest_pycollect_makeitem( + collector: "PyCollector", name: str, obj: object +) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]: + """Return a custom item/collector for a Python object in a module, or None. - Stops at first non-None result, see :ref:`firstresult` """ + Stops at first non-None result, see :ref:`firstresult`. + """ @hookspec(firstresult=True) -def pytest_pyfunc_call(pyfuncitem): - """ call underlying test function. +def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: + """Call underlying test function. - Stops at first non-None result, see :ref:`firstresult` """ + Stops at first non-None result, see :ref:`firstresult`. + """ -def pytest_generate_tests(metafunc): - """ generate (multiple) parametrized calls to a test function.""" +def pytest_generate_tests(metafunc: "Metafunc") -> None: + """Generate (multiple) parametrized calls to a test function.""" @hookspec(firstresult=True) -def pytest_make_parametrize_id(config, val, argname): - """Return a user-friendly string representation of the given ``val`` that will be used - by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``. +def pytest_make_parametrize_id( + config: "Config", val: object, argname: str +) -> Optional[str]: + """Return a user-friendly string representation of the given ``val`` + that will be used by @pytest.mark.parametrize calls, or None if the hook + doesn't know about ``val``. + The parameter name is available as ``argname``, if required. - Stops at first non-None result, see :ref:`firstresult` + Stops at first non-None result, see :ref:`firstresult`. - :param _pytest.config.Config config: pytest config object - :param val: the parametrized value - :param str argname: the automatic parameter name produced by pytest + :param _pytest.config.Config config: The pytest config object. + :param val: The parametrized value. + :param str argname: The automatic parameter name produced by pytest. """ # ------------------------------------------------------------------------- -# generic runtest related hooks +# runtest related hooks # ------------------------------------------------------------------------- @hookspec(firstresult=True) -def pytest_runtestloop(session): - """ called for performing the main runtest loop - (after collection finished). +def pytest_runtestloop(session: "Session") -> Optional[object]: + """Perform the main runtest loop (after collection finished). - Stops at first non-None result, see :ref:`firstresult` + The default hook implementation performs the runtest protocol for all items + collected in the session (``session.items``), unless the collection failed + or the ``collectonly`` pytest option is set. - :param _pytest.main.Session session: the pytest session object - """ + If at any point :py:func:`pytest.exit` is called, the loop is + terminated immediately. + + If at any point ``session.shouldfail`` or ``session.shouldstop`` are set, the + loop is terminated after the runtest protocol for the current item is finished. + :param pytest.Session session: The pytest session object. -def pytest_itemstart(item, node): - """(**Deprecated**) use pytest_runtest_logstart. """ + Stops at first non-None result, see :ref:`firstresult`. + The return value is not used, but only stops further processing. + """ @hookspec(firstresult=True) -def pytest_runtest_protocol(item, nextitem): - """ implements the runtest_setup/call/teardown protocol for - the given test item, including capturing exceptions and calling - reporting hooks. +def pytest_runtest_protocol( + item: "Item", nextitem: "Optional[Item]" +) -> Optional[object]: + """Perform the runtest protocol for a single test item. - :arg item: test item for which the runtest protocol is performed. + The default runtest protocol is this (see individual hooks for full details): - :arg nextitem: the scheduled-to-be-next test item (or None if this - is the end my friend). This argument is passed on to - :py:func:`pytest_runtest_teardown`. + - ``pytest_runtest_logstart(nodeid, location)`` - :return boolean: True if no further hook implementations should be invoked. + - Setup phase: + - ``call = pytest_runtest_setup(item)`` (wrapped in ``CallInfo(when="setup")``) + - ``report = pytest_runtest_makereport(item, call)`` + - ``pytest_runtest_logreport(report)`` + - ``pytest_exception_interact(call, report)`` if an interactive exception occurred + - Call phase, if the the setup passed and the ``setuponly`` pytest option is not set: + - ``call = pytest_runtest_call(item)`` (wrapped in ``CallInfo(when="call")``) + - ``report = pytest_runtest_makereport(item, call)`` + - ``pytest_runtest_logreport(report)`` + - ``pytest_exception_interact(call, report)`` if an interactive exception occurred - Stops at first non-None result, see :ref:`firstresult` """ + - Teardown phase: + - ``call = pytest_runtest_teardown(item, nextitem)`` (wrapped in ``CallInfo(when="teardown")``) + - ``report = pytest_runtest_makereport(item, call)`` + - ``pytest_runtest_logreport(report)`` + - ``pytest_exception_interact(call, report)`` if an interactive exception occurred + - ``pytest_runtest_logfinish(nodeid, location)`` -def pytest_runtest_logstart(nodeid, location): - """ signal the start of running a single test item. + :param item: Test item for which the runtest protocol is performed. + :param nextitem: The scheduled-to-be-next test item (or None if this is the end my friend). - This hook will be called **before** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and - :func:`pytest_runtest_teardown` hooks. - - :param str nodeid: full id of the item - :param location: a triple of ``(filename, linenum, testname)`` + Stops at first non-None result, see :ref:`firstresult`. + The return value is not used, but only stops further processing. """ -def pytest_runtest_logfinish(nodeid, location): - """ signal the complete finish of running a single test item. +def pytest_runtest_logstart( + nodeid: str, location: Tuple[str, Optional[int], str] +) -> None: + """Called at the start of running the runtest protocol for a single item. - This hook will be called **after** :func:`pytest_runtest_setup`, :func:`pytest_runtest_call` and - :func:`pytest_runtest_teardown` hooks. + See :func:`pytest_runtest_protocol` for a description of the runtest protocol. - :param str nodeid: full id of the item - :param location: a triple of ``(filename, linenum, testname)`` + :param str nodeid: Full node ID of the item. + :param location: A triple of ``(filename, lineno, testname)``. """ -def pytest_runtest_setup(item): - """ called before ``pytest_runtest_call(item)``. """ +def pytest_runtest_logfinish( + nodeid: str, location: Tuple[str, Optional[int], str] +) -> None: + """Called at the end of running the runtest protocol for a single item. + See :func:`pytest_runtest_protocol` for a description of the runtest protocol. -def pytest_runtest_call(item): - """ called to execute the test ``item``. """ + :param str nodeid: Full node ID of the item. + :param location: A triple of ``(filename, lineno, testname)``. + """ -def pytest_runtest_teardown(item, nextitem): - """ called after ``pytest_runtest_call``. +def pytest_runtest_setup(item: "Item") -> None: + """Called to perform the setup phase for a test item. - :arg nextitem: the scheduled-to-be-next test item (None if no further - test item is scheduled). This argument can be used to - perform exact teardowns, i.e. calling just enough finalizers - so that nextitem only needs to call setup-functions. + The default implementation runs ``setup()`` on ``item`` and all of its + parents (which haven't been setup yet). This includes obtaining the + values of fixtures required by the item (which haven't been obtained + yet). """ -@hookspec(firstresult=True) -def pytest_runtest_makereport(item, call): - """ return a :py:class:`_pytest.runner.TestReport` object - for the given :py:class:`pytest.Item <_pytest.main.Item>` and - :py:class:`_pytest.runner.CallInfo`. +def pytest_runtest_call(item: "Item") -> None: + """Called to run the test for test item (the call phase). + + The default implementation calls ``item.runtest()``. + """ + - Stops at first non-None result, see :ref:`firstresult` """ +def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: + """Called to perform the teardown phase for a test item. + The default implementation runs the finalizers and calls ``teardown()`` + on ``item`` and all of its parents (which need to be torn down). This + includes running the teardown phase of fixtures required by the item (if + they go out of scope). -def pytest_runtest_logreport(report): - """ process a test setup/call/teardown report relating to - the respective phase of executing a test. """ + :param nextitem: + The scheduled-to-be-next test item (None if no further test item is + scheduled). This argument can be used to perform exact teardowns, + i.e. calling just enough finalizers so that nextitem only needs to + call setup-functions. + """ @hookspec(firstresult=True) -def pytest_report_to_serializable(config, report): - """ - .. warning:: - This hook is experimental and subject to change between pytest releases, even - bug fixes. +def pytest_runtest_makereport( + item: "Item", call: "CallInfo[None]" +) -> Optional["TestReport"]: + """Called to create a :py:class:`_pytest.reports.TestReport` for each of + the setup, call and teardown runtest phases of a test item. - The intent is for this to be used by plugins maintained by the core-devs, such - as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal - 'resultlog' plugin. + See :func:`pytest_runtest_protocol` for a description of the runtest protocol. - In the future it might become part of the public hook API. + :param CallInfo[None] call: The ``CallInfo`` for the phase. - Serializes the given report object into a data structure suitable for sending - over the wire, e.g. converted to JSON. + Stops at first non-None result, see :ref:`firstresult`. """ -@hookspec(firstresult=True) -def pytest_report_from_serializable(config, data): +def pytest_runtest_logreport(report: "TestReport") -> None: + """Process the :py:class:`_pytest.reports.TestReport` produced for each + of the setup, call and teardown runtest phases of an item. + + See :func:`pytest_runtest_protocol` for a description of the runtest protocol. """ - .. warning:: - This hook is experimental and subject to change between pytest releases, even - bug fixes. - The intent is for this to be used by plugins maintained by the core-devs, such - as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal - 'resultlog' plugin. - In the future it might become part of the public hook API. +@hookspec(firstresult=True) +def pytest_report_to_serializable( + config: "Config", report: Union["CollectReport", "TestReport"], +) -> Optional[Dict[str, Any]]: + """Serialize the given report object into a data structure suitable for + sending over the wire, e.g. converted to JSON.""" - Restores a report object previously serialized with pytest_report_to_serializable(). - """ + +@hookspec(firstresult=True) +def pytest_report_from_serializable( + config: "Config", data: Dict[str, Any], +) -> Optional[Union["CollectReport", "TestReport"]]: + """Restore a report object previously serialized with pytest_report_to_serializable().""" # ------------------------------------------------------------------------- @@ -421,12 +539,14 @@ def pytest_report_from_serializable(config, data): @hookspec(firstresult=True) -def pytest_fixture_setup(fixturedef, request): - """ performs fixture setup execution. +def pytest_fixture_setup( + fixturedef: "FixtureDef[Any]", request: "SubRequest" +) -> Optional[object]: + """Perform fixture setup execution. - :return: The return value of the call to the fixture function + :returns: The return value of the call to the fixture function. - Stops at first non-None result, see :ref:`firstresult` + Stops at first non-None result, see :ref:`firstresult`. .. note:: If the fixture function returns None, other implementations of @@ -435,10 +555,12 @@ def pytest_fixture_setup(fixturedef, request): """ -def pytest_fixture_post_finalizer(fixturedef, request): - """ called after fixture teardown, but before the cache is cleared so - the fixture result cache ``fixturedef.cached_result`` can - still be accessed.""" +def pytest_fixture_post_finalizer( + fixturedef: "FixtureDef[Any]", request: "SubRequest" +) -> None: + """Called after fixture teardown, but before the cache is cleared, so + the fixture result ``fixturedef.cached_result`` is still available (not + ``None``).""" # ------------------------------------------------------------------------- @@ -446,26 +568,28 @@ def pytest_fixture_post_finalizer(fixturedef, request): # ------------------------------------------------------------------------- -def pytest_sessionstart(session): - """ called after the ``Session`` object has been created and before performing collection +def pytest_sessionstart(session: "Session") -> None: + """Called after the ``Session`` object has been created and before performing collection and entering the run test loop. - :param _pytest.main.Session session: the pytest session object + :param pytest.Session session: The pytest session object. """ -def pytest_sessionfinish(session, exitstatus): - """ called after whole test run finished, right before returning the exit status to the system. +def pytest_sessionfinish( + session: "Session", exitstatus: Union[int, "ExitCode"], +) -> None: + """Called after whole test run finished, right before returning the exit status to the system. - :param _pytest.main.Session session: the pytest session object - :param int exitstatus: the status which pytest will return to the system + :param pytest.Session session: The pytest session object. + :param int exitstatus: The status which pytest will return to the system. """ -def pytest_unconfigure(config): - """ called before test process is exited. +def pytest_unconfigure(config: "Config") -> None: + """Called before test process is exited. - :param _pytest.config.Config config: pytest config object + :param _pytest.config.Config config: The pytest config object. """ @@ -474,28 +598,74 @@ def pytest_unconfigure(config): # ------------------------------------------------------------------------- -def pytest_assertrepr_compare(config, op, left, right): - """return explanation for comparisons in failing assert expressions. +def pytest_assertrepr_compare( + config: "Config", op: str, left: object, right: object +) -> Optional[List[str]]: + """Return explanation for comparisons in failing assert expressions. Return None for no custom explanation, otherwise return a list - of strings. The strings will be joined by newlines but any newlines - *in* a string will be escaped. Note that all but the first line will + of strings. The strings will be joined by newlines but any newlines + *in* a string will be escaped. Note that all but the first line will be indented slightly, the intention is for the first line to be a summary. - :param _pytest.config.Config config: pytest config object + :param _pytest.config.Config config: The pytest config object. + """ + + +def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None: + """**(Experimental)** Called whenever an assertion passes. + + .. versionadded:: 5.0 + + Use this hook to do some processing after a passing assertion. + The original assertion information is available in the `orig` string + and the pytest introspected assertion information is available in the + `expl` string. + + This hook must be explicitly enabled by the ``enable_assertion_pass_hook`` + ini-file option: + + .. code-block:: ini + + [pytest] + enable_assertion_pass_hook=true + + You need to **clean the .pyc** files in your project directory and interpreter libraries + when enabling this option, as assertions will require to be re-written. + + :param pytest.Item item: pytest item object of current test. + :param int lineno: Line number of the assert statement. + :param str orig: String with the original assertion. + :param str expl: String with the assert explanation. + + .. note:: + + This hook is **experimental**, so its parameters or even the hook itself might + be changed/removed without warning in any future pytest release. + + If you find this hook useful, please share your feedback in an issue. """ # ------------------------------------------------------------------------- -# hooks for influencing reporting (invoked from _pytest_terminal) +# Hooks for influencing reporting (invoked from _pytest_terminal). # ------------------------------------------------------------------------- -def pytest_report_header(config, startdir): - """ return a string or list of strings to be displayed as header info for terminal reporting. +def pytest_report_header( + config: "Config", startdir: py.path.local +) -> Union[str, List[str]]: + """Return a string or list of strings to be displayed as header info for terminal reporting. + + :param _pytest.config.Config config: The pytest config object. + :param py.path.local startdir: The starting dir. + + .. note:: - :param _pytest.config.Config config: pytest config object - :param startdir: py.path object with the starting dir + Lines returned by a plugin are displayed before those of plugins which + ran before it. + If you want to have your line(s) displayed first, use + :ref:`trylast=True <plugin-hookorder>`. .. note:: @@ -505,66 +675,85 @@ def pytest_report_header(config, startdir): """ -def pytest_report_collectionfinish(config, startdir, items): - """ +def pytest_report_collectionfinish( + config: "Config", startdir: py.path.local, items: Sequence["Item"], +) -> Union[str, List[str]]: + """Return a string or list of strings to be displayed after collection + has finished successfully. + + These strings will be displayed after the standard "collected X items" message. + .. versionadded:: 3.2 - return a string or list of strings to be displayed after collection has finished successfully. + :param _pytest.config.Config config: The pytest config object. + :param py.path.local startdir: The starting dir. + :param items: List of pytest items that are going to be executed; this list should not be modified. - This strings will be displayed after the standard "collected X items" message. + .. note:: - :param _pytest.config.Config config: pytest config object - :param startdir: py.path object with the starting dir - :param items: list of pytest items that are going to be executed; this list should not be modified. + Lines returned by a plugin are displayed before those of plugins which + ran before it. + If you want to have your line(s) displayed first, use + :ref:`trylast=True <plugin-hookorder>`. """ @hookspec(firstresult=True) -def pytest_report_teststatus(report, config): - """ return result-category, shortletter and verbose word for reporting. +def pytest_report_teststatus( + report: Union["CollectReport", "TestReport"], config: "Config" +) -> Tuple[ + str, str, Union[str, Mapping[str, bool]], +]: + """Return result-category, shortletter and verbose word for status + reporting. - :param _pytest.config.Config config: pytest config object + The result-category is a category in which to count the result, for + example "passed", "skipped", "error" or the empty string. - Stops at first non-None result, see :ref:`firstresult` """ + The shortletter is shown as testing progresses, for example ".", "s", + "E" or the empty string. + The verbose word is shown as testing progresses in verbose mode, for + example "PASSED", "SKIPPED", "ERROR" or the empty string. -def pytest_terminal_summary(terminalreporter, exitstatus, config): - """Add a section to terminal summary reporting. + pytest may style these implicitly according to the report outcome. + To provide explicit styling, return a tuple for the verbose word, + for example ``"rerun", "R", ("RERUN", {"yellow": True})``. - :param _pytest.terminal.TerminalReporter terminalreporter: the internal terminal reporter object - :param int exitstatus: the exit status that will be reported back to the OS - :param _pytest.config.Config config: pytest config object + :param report: The report object whose status is to be returned. + :param _pytest.config.Config config: The pytest config object. - .. versionadded:: 4.2 - The ``config`` parameter. + Stops at first non-None result, see :ref:`firstresult`. """ -@hookspec(historic=True, warn_on_impl=PYTEST_LOGWARNING) -def pytest_logwarning(message, code, nodeid, fslocation): - """ - .. deprecated:: 3.8 +def pytest_terminal_summary( + terminalreporter: "TerminalReporter", exitstatus: "ExitCode", config: "Config", +) -> None: + """Add a section to terminal summary reporting. - This hook is will stop working in a future release. + :param _pytest.terminal.TerminalReporter terminalreporter: The internal terminal reporter object. + :param int exitstatus: The exit status that will be reported back to the OS. + :param _pytest.config.Config config: The pytest config object. - pytest no longer triggers this hook, but the - terminal writer still implements it to display warnings issued by - :meth:`_pytest.config.Config.warn` and :meth:`_pytest.nodes.Node.warn`. Calling those functions will be - an error in future releases. + .. versionadded:: 4.2 + The ``config`` parameter. + """ - process a warning specified by a message, a code string, - a nodeid and fslocation (both of which may be None - if the warning is not tied to a particular node/location). - .. note:: - This hook is incompatible with ``hookwrapper=True``. - """ +@hookspec(historic=True, warn_on_impl=WARNING_CAPTURED_HOOK) +def pytest_warning_captured( + warning_message: "warnings.WarningMessage", + when: "Literal['config', 'collect', 'runtest']", + item: Optional["Item"], + location: Optional[Tuple[str, int, str]], +) -> None: + """(**Deprecated**) Process a warning captured by the internal pytest warnings plugin. + .. deprecated:: 6.0 -@hookspec(historic=True) -def pytest_warning_captured(warning_message, when, item): - """ - Process a warning captured by the internal pytest warnings plugin. + This hook is considered deprecated and will be removed in a future pytest version. + Use :func:`pytest_warning_recorded` instead. :param warnings.WarningMessage warning_message: The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains @@ -578,23 +767,45 @@ def pytest_warning_captured(warning_message, when, item): * ``"runtest"``: during test execution. :param pytest.Item|None item: - **DEPRECATED**: This parameter is incompatible with ``pytest-xdist``, and will always receive ``None`` - in a future release. - The item being executed if ``when`` is ``"runtest"``, otherwise ``None``. + + :param tuple location: + When available, holds information about the execution context of the captured + warning (filename, linenumber, function). ``function`` evaluates to <module> + when the execution context is at the module level. """ -# ------------------------------------------------------------------------- -# doctest hooks -# ------------------------------------------------------------------------- +@hookspec(historic=True) +def pytest_warning_recorded( + warning_message: "warnings.WarningMessage", + when: "Literal['config', 'collect', 'runtest']", + nodeid: str, + location: Optional[Tuple[str, int, str]], +) -> None: + """Process a warning captured by the internal pytest warnings plugin. + :param warnings.WarningMessage warning_message: + The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains + the same attributes as the parameters of :py:func:`warnings.showwarning`. -@hookspec(firstresult=True) -def pytest_doctest_prepare_content(content): - """ return processed content for a given doctest + :param str when: + Indicates when the warning was captured. Possible values: + + * ``"config"``: during pytest configuration/initialization stage. + * ``"collect"``: during test collection. + * ``"runtest"``: during test execution. + + :param str nodeid: + Full id of the item. - Stops at first non-None result, see :ref:`firstresult` """ + :param tuple|None location: + When available, holds information about the execution context of the captured + warning (filename, linenumber, function). ``function`` evaluates to <module> + when the execution context is at the module level. + + .. versionadded:: 6.0 + """ # ------------------------------------------------------------------------- @@ -602,38 +813,58 @@ def pytest_doctest_prepare_content(content): # ------------------------------------------------------------------------- -def pytest_internalerror(excrepr, excinfo): - """ called for internal errors. """ +def pytest_internalerror( + excrepr: "ExceptionRepr", excinfo: "ExceptionInfo[BaseException]", +) -> Optional[bool]: + """Called for internal errors. + + Return True to suppress the fallback handling of printing an + INTERNALERROR message directly to sys.stderr. + """ -def pytest_keyboard_interrupt(excinfo): - """ called for keyboard interrupt. """ +def pytest_keyboard_interrupt( + excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]", +) -> None: + """Called for keyboard interrupt.""" -def pytest_exception_interact(node, call, report): - """called when an exception was raised which can potentially be +def pytest_exception_interact( + node: Union["Item", "Collector"], + call: "CallInfo[Any]", + report: Union["CollectReport", "TestReport"], +) -> None: + """Called when an exception was raised which can potentially be interactively handled. - This hook is only called if an exception was raised - that is not an internal exception like ``skip.Exception``. + May be called during collection (see :py:func:`pytest_make_collect_report`), + in which case ``report`` is a :py:class:`_pytest.reports.CollectReport`. + + May be called during runtest of an item (see :py:func:`pytest_runtest_protocol`), + in which case ``report`` is a :py:class:`_pytest.reports.TestReport`. + + This hook is not called if the exception that was raised is an internal + exception like ``skip.Exception``. """ -def pytest_enter_pdb(config, pdb): - """ called upon pdb.set_trace(), can be used by plugins to take special - action just before the python debugger enters in interactive mode. +def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: + """Called upon pdb.set_trace(). + + Can be used by plugins to take special action just before the python + debugger enters interactive mode. - :param _pytest.config.Config config: pytest config object - :param pdb.Pdb pdb: Pdb instance + :param _pytest.config.Config config: The pytest config object. + :param pdb.Pdb pdb: The Pdb instance. """ -def pytest_leave_pdb(config, pdb): - """ called when leaving pdb (e.g. with continue after pdb.set_trace()). +def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: + """Called when leaving pdb (e.g. with continue after pdb.set_trace()). Can be used by plugins to take special action just after the python debugger leaves interactive mode. - :param _pytest.config.Config config: pytest config object - :param pdb.Pdb pdb: Pdb instance + :param _pytest.config.Config config: The pytest config object. + :param pdb.Pdb pdb: The Pdb instance. """ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py index 853dcb77449..877b9be78bc 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/junitxml.py @@ -1,75 +1,70 @@ -# -*- coding: utf-8 -*- -""" - report test results in JUnit-XML format, - for use with Jenkins and build integration servers. - +"""Report test results in JUnit-XML format, for use with Jenkins and build +integration servers. Based on initial code from Ross Lawley. -Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/ -src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd +Output conforms to +https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import functools import os import platform import re -import sys -import time +import xml.etree.ElementTree as ET from datetime import datetime - -import py -import six +from typing import Callable +from typing import Dict +from typing import List +from typing import Match +from typing import Optional +from typing import Tuple +from typing import Union import pytest from _pytest import nodes +from _pytest import timing +from _pytest._code.code import ExceptionRepr +from _pytest._code.code import ReprFileLocation +from _pytest.config import Config from _pytest.config import filename_arg - -# Python 2.X and 3.X compatibility -if sys.version_info[0] < 3: - from codecs import open +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureRequest +from _pytest.reports import TestReport +from _pytest.store import StoreKey +from _pytest.terminal import TerminalReporter -class Junit(py.xml.Namespace): - pass +xml_key = StoreKey["LogXML"]() -# We need to get the subset of the invalid unicode ranges according to -# XML 1.0 which are valid in this python build. Hence we calculate -# this dynamically instead of hardcoding it. The spec range of valid -# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] -# | [#x10000-#x10FFFF] -_legal_chars = (0x09, 0x0A, 0x0D) -_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF)) -_legal_xml_re = [ - u"%s-%s" % (six.unichr(low), six.unichr(high)) - for (low, high) in _legal_ranges - if low < sys.maxunicode -] -_legal_xml_re = [six.unichr(x) for x in _legal_chars] + _legal_xml_re -illegal_xml_re = re.compile(u"[^%s]" % u"".join(_legal_xml_re)) -del _legal_chars -del _legal_ranges -del _legal_xml_re - -_py_ext_re = re.compile(r"\.py$") +def bin_xml_escape(arg: object) -> str: + r"""Visually escape invalid XML characters. + For example, transforms + 'hello\aworld\b' + into + 'hello#x07world#x08' + Note that the #xABs are *not* XML escapes - missing the ampersand «. + The idea is to escape visually for the user rather than for XML itself. + """ -def bin_xml_escape(arg): - def repl(matchobj): + def repl(matchobj: Match[str]) -> str: i = ord(matchobj.group()) if i <= 0xFF: - return u"#x%02X" % i + return "#x%02X" % i else: - return u"#x%04X" % i + return "#x%04X" % i - return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg))) + # The spec range of valid chars is: + # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + # For an unknown(?) reason, we disallow #x7F (DEL) as well. + illegal_xml_re = ( + "[^\u0009\u000A\u000D\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]" + ) + return re.sub(illegal_xml_re, repl, str(arg)) -def merge_family(left, right): +def merge_family(left, right) -> None: result = {} for kl, vl in left.items(): for kr, vr in right.items(): @@ -83,50 +78,45 @@ families = {} families["_base"] = {"testcase": ["classname", "name"]} families["_base_legacy"] = {"testcase": ["file", "line", "url"]} -# xUnit 1.x inherits legacy attributes +# xUnit 1.x inherits legacy attributes. families["xunit1"] = families["_base"].copy() merge_family(families["xunit1"], families["_base_legacy"]) -# xUnit 2.x uses strict base attributes +# xUnit 2.x uses strict base attributes. families["xunit2"] = families["_base"] -class _NodeReporter(object): - def __init__(self, nodeid, xml): +class _NodeReporter: + def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: self.id = nodeid self.xml = xml self.add_stats = self.xml.add_stats self.family = self.xml.family self.duration = 0 - self.properties = [] - self.nodes = [] - self.testcase = None - self.attrs = {} + self.properties = [] # type: List[Tuple[str, str]] + self.nodes = [] # type: List[ET.Element] + self.attrs = {} # type: Dict[str, str] - def append(self, node): - self.xml.add_stats(type(node).__name__) + def append(self, node: ET.Element) -> None: + self.xml.add_stats(node.tag) self.nodes.append(node) - def add_property(self, name, value): + def add_property(self, name: str, value: object) -> None: self.properties.append((str(name), bin_xml_escape(value))) - def add_attribute(self, name, value): + def add_attribute(self, name: str, value: object) -> None: self.attrs[str(name)] = bin_xml_escape(value) - def make_properties_node(self): - """Return a Junit node containing custom properties, if any. - """ + def make_properties_node(self) -> Optional[ET.Element]: + """Return a Junit node containing custom properties, if any.""" if self.properties: - return Junit.properties( - [ - Junit.property(name=name, value=value) - for name, value in self.properties - ] - ) - return "" + properties = ET.Element("properties") + for name, value in self.properties: + properties.append(ET.Element("property", name=name, value=value)) + return properties + return None - def record_testreport(self, testreport): - assert not self.testcase + def record_testreport(self, testreport: TestReport) -> None: names = mangle_test_address(testreport.nodeid) existing_attrs = self.attrs classnames = names[:-1] @@ -136,15 +126,15 @@ class _NodeReporter(object): "classname": ".".join(classnames), "name": bin_xml_escape(names[-1]), "file": testreport.location[0], - } + } # type: Dict[str, str] if testreport.location[1] is not None: - attrs["line"] = testreport.location[1] + attrs["line"] = str(testreport.location[1]) if hasattr(testreport, "url"): attrs["url"] = testreport.url self.attrs = attrs - self.attrs.update(existing_attrs) # restore any user-defined attributes + self.attrs.update(existing_attrs) # Restore any user-defined attributes. - # Preserve legacy testcase behavior + # Preserve legacy testcase behavior. if self.family == "xunit1": return @@ -156,142 +146,128 @@ class _NodeReporter(object): temp_attrs[key] = self.attrs[key] self.attrs = temp_attrs - def to_xml(self): - testcase = Junit.testcase(time="%.3f" % self.duration, **self.attrs) - testcase.append(self.make_properties_node()) - for node in self.nodes: - testcase.append(node) + def to_xml(self) -> ET.Element: + testcase = ET.Element("testcase", self.attrs, time="%.3f" % self.duration) + properties = self.make_properties_node() + if properties is not None: + testcase.append(properties) + testcase.extend(self.nodes) return testcase - def _add_simple(self, kind, message, data=None): - data = bin_xml_escape(data) - node = kind(data, message=message) + def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None: + node = ET.Element(tag, message=message) + node.text = bin_xml_escape(data) self.append(node) - def write_captured_output(self, report): + def write_captured_output(self, report: TestReport) -> None: if not self.xml.log_passing_tests and report.passed: return content_out = report.capstdout content_log = report.caplog content_err = report.capstderr - - if content_log or content_out: - if content_log and self.xml.logging == "system-out": - if content_out: - # syncing stdout and the log-output is not done yet. It's - # probably not worth the effort. Therefore, first the captured - # stdout is shown and then the captured logs. - content = "\n".join( - [ - " Captured Stdout ".center(80, "-"), - content_out, - "", - " Captured Log ".center(80, "-"), - content_log, - ] - ) - else: - content = content_log - else: - content = content_out - - if content: - tag = getattr(Junit, "system-out") - self.append(tag(bin_xml_escape(content))) - - if content_log or content_err: - if content_log and self.xml.logging == "system-err": - if content_err: - content = "\n".join( - [ - " Captured Stderr ".center(80, "-"), - content_err, - "", - " Captured Log ".center(80, "-"), - content_log, - ] - ) - else: - content = content_log - else: - content = content_err - - if content: - tag = getattr(Junit, "system-err") - self.append(tag(bin_xml_escape(content))) - - def append_pass(self, report): + if self.xml.logging == "no": + return + content_all = "" + if self.xml.logging in ["log", "all"]: + content_all = self._prepare_content(content_log, " Captured Log ") + if self.xml.logging in ["system-out", "out-err", "all"]: + content_all += self._prepare_content(content_out, " Captured Out ") + self._write_content(report, content_all, "system-out") + content_all = "" + if self.xml.logging in ["system-err", "out-err", "all"]: + content_all += self._prepare_content(content_err, " Captured Err ") + self._write_content(report, content_all, "system-err") + content_all = "" + if content_all: + self._write_content(report, content_all, "system-out") + + def _prepare_content(self, content: str, header: str) -> str: + return "\n".join([header.center(80, "-"), content, ""]) + + def _write_content(self, report: TestReport, content: str, jheader: str) -> None: + tag = ET.Element(jheader) + tag.text = bin_xml_escape(content) + self.append(tag) + + def append_pass(self, report: TestReport) -> None: self.add_stats("passed") - def append_failure(self, report): + def append_failure(self, report: TestReport) -> None: # msg = str(report.longrepr.reprtraceback.extraline) if hasattr(report, "wasxfail"): - self._add_simple(Junit.skipped, "xfail-marked test passes unexpectedly") + self._add_simple("skipped", "xfail-marked test passes unexpectedly") else: - if hasattr(report.longrepr, "reprcrash"): - message = report.longrepr.reprcrash.message - elif isinstance(report.longrepr, six.string_types): - message = report.longrepr + assert report.longrepr is not None + reprcrash = getattr( + report.longrepr, "reprcrash", None + ) # type: Optional[ReprFileLocation] + if reprcrash is not None: + message = reprcrash.message else: message = str(report.longrepr) message = bin_xml_escape(message) - fail = Junit.failure(message=message) - fail.append(bin_xml_escape(report.longrepr)) - self.append(fail) + self._add_simple("failure", message, str(report.longrepr)) - def append_collect_error(self, report): + def append_collect_error(self, report: TestReport) -> None: # msg = str(report.longrepr.reprtraceback.extraline) - self.append( - Junit.error(bin_xml_escape(report.longrepr), message="collection failure") - ) - - def append_collect_skipped(self, report): - self._add_simple(Junit.skipped, "collection skipped", report.longrepr) + assert report.longrepr is not None + self._add_simple("error", "collection failure", str(report.longrepr)) + + def append_collect_skipped(self, report: TestReport) -> None: + self._add_simple("skipped", "collection skipped", str(report.longrepr)) + + def append_error(self, report: TestReport) -> None: + assert report.longrepr is not None + reprcrash = getattr( + report.longrepr, "reprcrash", None + ) # type: Optional[ReprFileLocation] + if reprcrash is not None: + reason = reprcrash.message + else: + reason = str(report.longrepr) - def append_error(self, report): if report.when == "teardown": - msg = "test teardown failure" + msg = 'failed on teardown with "{}"'.format(reason) else: - msg = "test setup failure" - self._add_simple(Junit.error, msg, report.longrepr) + msg = 'failed on setup with "{}"'.format(reason) + self._add_simple("error", msg, str(report.longrepr)) - def append_skipped(self, report): + def append_skipped(self, report: TestReport) -> None: if hasattr(report, "wasxfail"): xfailreason = report.wasxfail if xfailreason.startswith("reason: "): xfailreason = xfailreason[8:] - self.append( - Junit.skipped( - "", type="pytest.xfail", message=bin_xml_escape(xfailreason) - ) - ) + xfailreason = bin_xml_escape(xfailreason) + skipped = ET.Element("skipped", type="pytest.xfail", message=xfailreason) + self.append(skipped) else: + assert isinstance(report.longrepr, tuple) filename, lineno, skipreason = report.longrepr if skipreason.startswith("Skipped: "): skipreason = skipreason[9:] - details = "%s:%s: %s" % (filename, lineno, skipreason) + details = "{}:{}: {}".format(filename, lineno, skipreason) - self.append( - Junit.skipped( - bin_xml_escape(details), - type="pytest.skip", - message=bin_xml_escape(skipreason), - ) - ) + skipped = ET.Element("skipped", type="pytest.skip", message=skipreason) + skipped.text = bin_xml_escape(details) + self.append(skipped) self.write_captured_output(report) - def finalize(self): - data = self.to_xml().unicode(indent=0) + def finalize(self) -> None: + data = self.to_xml() self.__dict__.clear() - self.to_xml = lambda: py.xml.raw(data) + # Type ignored becuase mypy doesn't like overriding a method. + # Also the return value doesn't match... + self.to_xml = lambda: data # type: ignore[assignment] -def _warn_incompatibility_with_xunit2(request, fixture_name): - """Emits a PytestWarning about the given fixture being incompatible with newer xunit revisions""" +def _warn_incompatibility_with_xunit2( + request: FixtureRequest, fixture_name: str +) -> None: + """Emit a PytestWarning about the given fixture being incompatible with newer xunit revisions.""" from _pytest.warning_types import PytestWarning - xml = getattr(request.config, "_xml", None) + xml = request.config._store.get(xml_key, None) if xml is not None and xml.family not in ("xunit1", "legacy"): request.node.warn( PytestWarning( @@ -303,12 +279,14 @@ def _warn_incompatibility_with_xunit2(request, fixture_name): @pytest.fixture -def record_property(request): - """Add an extra properties the calling test. +def record_property(request: FixtureRequest) -> Callable[[str, object], None]: + """Add extra properties to the calling test. + User properties become part of the test report and are available to the configured reporters, like JUnit XML. - The fixture is callable with ``(name, value)``, with value being automatically - xml-encoded. + + The fixture is callable with ``name, value``. The value is automatically + XML-encoded. Example:: @@ -317,17 +295,18 @@ def record_property(request): """ _warn_incompatibility_with_xunit2(request, "record_property") - def append_property(name, value): + def append_property(name: str, value: object) -> None: request.node.user_properties.append((name, value)) return append_property @pytest.fixture -def record_xml_attribute(request): +def record_xml_attribute(request: FixtureRequest) -> Callable[[str, object], None]: """Add extra xml attributes to the tag for the calling test. - The fixture is callable with ``(name, value)``, with value being - automatically xml-encoded + + The fixture is callable with ``name, value``. The value is + automatically XML-encoded. """ from _pytest.warning_types import PytestExperimentalApiWarning @@ -338,12 +317,12 @@ def record_xml_attribute(request): _warn_incompatibility_with_xunit2(request, "record_xml_attribute") # Declare noop - def add_attr_noop(name, value): + def add_attr_noop(name: str, value: object) -> None: pass attr_func = add_attr_noop - xml = getattr(request.config, "_xml", None) + xml = request.config._store.get(xml_key, None) if xml is not None: node_reporter = xml.node_reporter(request.node.nodeid) attr_func = node_reporter.add_attribute @@ -351,20 +330,21 @@ def record_xml_attribute(request): return attr_func -def _check_record_param_type(param, v): +def _check_record_param_type(param: str, v: str) -> None: """Used by record_testsuite_property to check that the given parameter name is of the proper - type""" + type.""" __tracebackhide__ = True - if not isinstance(v, six.string_types): - msg = "{param} parameter needs to be a string, but {g} given" + if not isinstance(v, str): + msg = "{param} parameter needs to be a string, but {g} given" # type: ignore[unreachable] raise TypeError(msg.format(param=param, g=type(v).__name__)) @pytest.fixture(scope="session") -def record_testsuite_property(request): - """ - Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to - writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family. +def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object], None]: + """Record a new ``<property>`` tag as child of the root ``<testsuite>``. + + This is suitable to writing global information regarding the entire test + suite, and is compatible with ``xunit2`` JUnit family. This is a ``session``-scoped fixture which is called with ``(name, value)``. Example: @@ -375,22 +355,28 @@ def record_testsuite_property(request): record_testsuite_property("STORAGE_TYPE", "CEPH") ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. + + .. warning:: + + Currently this fixture **does not work** with the + `pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See issue + `#7767 <https://github.com/pytest-dev/pytest/issues/7767>`__ for details. """ __tracebackhide__ = True - def record_func(name, value): - """noop function in case --junitxml was not passed in the command-line""" + def record_func(name: str, value: object) -> None: + """No-op function in case --junitxml was not passed in the command-line.""" __tracebackhide__ = True _check_record_param_type("name", name) - xml = getattr(request.config, "_xml", None) + xml = request.config._store.get(xml_key, None) if xml is not None: record_func = xml.add_global_property # noqa return record_func -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting") group.addoption( "--junitxml", @@ -416,9 +402,9 @@ def pytest_addoption(parser): parser.addini( "junit_logging", "Write captured log messages to JUnit report: " - "one of no|system-out|system-err", + "one of no|log|system-out|system-err|out-err|all", default="no", - ) # choices=['no', 'stdout', 'stderr']) + ) parser.addini( "junit_log_passing_tests", "Capture log information for passing tests to JUnit report: ", @@ -433,59 +419,60 @@ def pytest_addoption(parser): parser.addini( "junit_family", "Emit XML for schema: one of legacy|xunit1|xunit2", - default="xunit1", + default="xunit2", ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: xmlpath = config.option.xmlpath - # prevent opening xmllog on slave nodes (xdist) - if xmlpath and not hasattr(config, "slaveinput"): - config._xml = LogXML( + # Prevent opening xmllog on worker nodes (xdist). + if xmlpath and not hasattr(config, "workerinput"): + junit_family = config.getini("junit_family") + config._store[xml_key] = LogXML( xmlpath, config.option.junitprefix, config.getini("junit_suite_name"), config.getini("junit_logging"), config.getini("junit_duration_report"), - config.getini("junit_family"), + junit_family, config.getini("junit_log_passing_tests"), ) - config.pluginmanager.register(config._xml) + config.pluginmanager.register(config._store[xml_key]) -def pytest_unconfigure(config): - xml = getattr(config, "_xml", None) +def pytest_unconfigure(config: Config) -> None: + xml = config._store.get(xml_key, None) if xml: - del config._xml + del config._store[xml_key] config.pluginmanager.unregister(xml) -def mangle_test_address(address): +def mangle_test_address(address: str) -> List[str]: path, possible_open_bracket, params = address.partition("[") names = path.split("::") try: names.remove("()") except ValueError: pass - # convert file path to dotted path + # Convert file path to dotted path. names[0] = names[0].replace(nodes.SEP, ".") - names[0] = _py_ext_re.sub("", names[0]) - # put any params back + names[0] = re.sub(r"\.py$", "", names[0]) + # Put any params back. names[-1] += possible_open_bracket + params return names -class LogXML(object): +class LogXML: def __init__( self, logfile, - prefix, - suite_name="pytest", - logging="no", - report_duration="total", + prefix: Optional[str], + suite_name: str = "pytest", + logging: str = "no", + report_duration: str = "total", family="xunit1", - log_passing_tests=True, - ): + log_passing_tests: bool = True, + ) -> None: logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.normpath(os.path.abspath(logfile)) self.prefix = prefix @@ -494,36 +481,40 @@ class LogXML(object): self.log_passing_tests = log_passing_tests self.report_duration = report_duration self.family = family - self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0) - self.node_reporters = {} # nodeid -> _NodeReporter - self.node_reporters_ordered = [] - self.global_properties = [] + self.stats = dict.fromkeys( + ["error", "passed", "failure", "skipped"], 0 + ) # type: Dict[str, int] + self.node_reporters = ( + {} + ) # type: Dict[Tuple[Union[str, TestReport], object], _NodeReporter] + self.node_reporters_ordered = [] # type: List[_NodeReporter] + self.global_properties = [] # type: List[Tuple[str, str]] # List of reports that failed on call but teardown is pending. - self.open_reports = [] + self.open_reports = [] # type: List[TestReport] self.cnt_double_fail_tests = 0 - # Replaces convenience family with real family + # Replaces convenience family with real family. if self.family == "legacy": self.family = "xunit1" - def finalize(self, report): + def finalize(self, report: TestReport) -> None: nodeid = getattr(report, "nodeid", report) - # local hack to handle xdist report order - slavenode = getattr(report, "node", None) - reporter = self.node_reporters.pop((nodeid, slavenode)) + # Local hack to handle xdist report order. + workernode = getattr(report, "node", None) + reporter = self.node_reporters.pop((nodeid, workernode)) if reporter is not None: reporter.finalize() - def node_reporter(self, report): - nodeid = getattr(report, "nodeid", report) - # local hack to handle xdist report order - slavenode = getattr(report, "node", None) + def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter: + nodeid = getattr(report, "nodeid", report) # type: Union[str, TestReport] + # Local hack to handle xdist report order. + workernode = getattr(report, "node", None) - key = nodeid, slavenode + key = nodeid, workernode if key in self.node_reporters: - # TODO: breasks for --dist=each + # TODO: breaks for --dist=each return self.node_reporters[key] reporter = _NodeReporter(nodeid, self) @@ -533,23 +524,23 @@ class LogXML(object): return reporter - def add_stats(self, key): + def add_stats(self, key: str) -> None: if key in self.stats: self.stats[key] += 1 - def _opentestcase(self, report): + def _opentestcase(self, report: TestReport) -> _NodeReporter: reporter = self.node_reporter(report) reporter.record_testreport(report) return reporter - def pytest_runtest_logreport(self, report): - """handle a setup/call/teardown report, generating the appropriate - xml tags as necessary. + def pytest_runtest_logreport(self, report: TestReport) -> None: + """Handle a setup/call/teardown report, generating the appropriate + XML tags as necessary. - note: due to plugins like xdist, this hook may be called in interlaced - order with reports from other nodes. for example: + Note: due to plugins like xdist, this hook may be called in interlaced + order with reports from other nodes. For example: - usual call order: + Usual call order: -> setup node1 -> call node1 -> teardown node1 @@ -557,7 +548,7 @@ class LogXML(object): -> call node2 -> teardown node2 - possible call order in xdist: + Possible call order in xdist: -> setup node1 -> call node1 -> setup node2 @@ -572,7 +563,7 @@ class LogXML(object): reporter.append_pass(report) elif report.failed: if report.when == "teardown": - # The following vars are needed when xdist plugin is used + # The following vars are needed when xdist plugin is used. report_wid = getattr(report, "worker_id", None) report_ii = getattr(report, "item_index", None) close_report = next( @@ -590,7 +581,7 @@ class LogXML(object): if close_report: # We need to open new testcase in case we have failure in # call and error in teardown in order to follow junit - # schema + # schema. self.finalize(close_report) self.cnt_double_fail_tests += 1 reporter = self._opentestcase(report) @@ -610,7 +601,7 @@ class LogXML(object): reporter.write_captured_output(report) for propname, propvalue in report.user_properties: - reporter.add_property(propname, propvalue) + reporter.add_property(propname, str(propvalue)) self.finalize(report) report_wid = getattr(report, "worker_id", None) @@ -630,15 +621,14 @@ class LogXML(object): if close_report: self.open_reports.remove(close_report) - def update_testcase_duration(self, report): - """accumulates total duration for nodeid from given report and updates - the Junit.testcase with the new total if already created. - """ + def update_testcase_duration(self, report: TestReport) -> None: + """Accumulate total duration for nodeid from given report and update + the Junit.testcase with the new total if already created.""" if self.report_duration == "total" or report.when == self.report_duration: reporter = self.node_reporter(report) reporter.duration += getattr(report, "duration", 0.0) - def pytest_collectreport(self, report): + def pytest_collectreport(self, report: TestReport) -> None: if not report.passed: reporter = self._opentestcase(report) if report.failed: @@ -646,20 +636,20 @@ class LogXML(object): else: reporter.append_collect_skipped(report) - def pytest_internalerror(self, excrepr): + def pytest_internalerror(self, excrepr: ExceptionRepr) -> None: reporter = self.node_reporter("internal") reporter.attrs.update(classname="pytest", name="internal") - reporter._add_simple(Junit.error, "internal error", excrepr) + reporter._add_simple("error", "internal error", str(excrepr)) - def pytest_sessionstart(self): - self.suite_start_time = time.time() + def pytest_sessionstart(self) -> None: + self.suite_start_time = timing.time() - def pytest_sessionfinish(self): + def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) if not os.path.isdir(dirname): os.makedirs(dirname) logfile = open(self.logfile, "w", encoding="utf-8") - suite_stop_time = time.time() + suite_stop_time = timing.time() suite_time_delta = suite_stop_time - self.suite_start_time numtests = ( @@ -671,37 +661,40 @@ class LogXML(object): ) logfile.write('<?xml version="1.0" encoding="utf-8"?>') - suite_node = Junit.testsuite( - self._get_global_properties_node(), - [x.to_xml() for x in self.node_reporters_ordered], + suite_node = ET.Element( + "testsuite", name=self.suite_name, - errors=self.stats["error"], - failures=self.stats["failure"], - skipped=self.stats["skipped"], - tests=numtests, + errors=str(self.stats["error"]), + failures=str(self.stats["failure"]), + skipped=str(self.stats["skipped"]), + tests=str(numtests), time="%.3f" % suite_time_delta, timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(), hostname=platform.node(), ) - logfile.write(Junit.testsuites([suite_node]).unicode(indent=0)) + global_properties = self._get_global_properties_node() + if global_properties is not None: + suite_node.append(global_properties) + for node_reporter in self.node_reporters_ordered: + suite_node.append(node_reporter.to_xml()) + testsuites = ET.Element("testsuites") + testsuites.append(suite_node) + logfile.write(ET.tostring(testsuites, encoding="unicode")) logfile.close() - def pytest_terminal_summary(self, terminalreporter): - terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile)) + def pytest_terminal_summary(self, terminalreporter: TerminalReporter) -> None: + terminalreporter.write_sep("-", "generated xml file: {}".format(self.logfile)) - def add_global_property(self, name, value): + def add_global_property(self, name: str, value: object) -> None: __tracebackhide__ = True _check_record_param_type("name", name) self.global_properties.append((name, bin_xml_escape(value))) - def _get_global_properties_node(self): - """Return a Junit node containing custom properties, if any. - """ + def _get_global_properties_node(self) -> Optional[ET.Element]: + """Return a Junit node containing custom properties, if any.""" if self.global_properties: - return Junit.properties( - [ - Junit.property(name=name, value=value) - for name, value in self.global_properties - ] - ) - return "" + properties = ET.Element("properties") + for name, value in self.global_properties: + properties.append(ET.Element("property", name=name, value=value)) + return properties + return None diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py index 2400737ee4e..c277ba5320c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/logging.py @@ -1,34 +1,51 @@ -# -*- coding: utf-8 -*- -""" Access and control log capturing. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Access and control log capturing.""" import logging +import os import re +import sys from contextlib import contextmanager - -import py -import six +from io import StringIO +from typing import AbstractSet +from typing import Dict +from typing import Generator +from typing import List +from typing import Mapping +from typing import Optional +from typing import Tuple +from typing import TypeVar +from typing import Union import pytest -from _pytest.compat import dummy_context_manager +from _pytest import nodes +from _pytest._io import TerminalWriter +from _pytest.capture import CaptureManager +from _pytest.compat import final +from _pytest.compat import nullcontext +from _pytest.config import _strtobool +from _pytest.config import Config from _pytest.config import create_terminal_writer +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureRequest +from _pytest.main import Session from _pytest.pathlib import Path +from _pytest.store import StoreKey +from _pytest.terminal import TerminalReporter + DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s" DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" _ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m") +caplog_handler_key = StoreKey["LogCaptureHandler"]() +caplog_records_key = StoreKey[Dict[str, List[logging.LogRecord]]]() -def _remove_ansi_escape_sequences(text): +def _remove_ansi_escape_sequences(text: str) -> str: return _ANSI_ESCAPE_SEQ.sub("", text) class ColoredLevelFormatter(logging.Formatter): - """ - Colorize the %(levelname)..s part of the log format passed to __init__. - """ + """A logging formatter which colorizes the %(levelname)..s part of the + log format passed to __init__.""" LOGLEVEL_COLOROPTS = { logging.CRITICAL: {"red"}, @@ -38,17 +55,15 @@ class ColoredLevelFormatter(logging.Formatter): logging.INFO: {"green"}, logging.DEBUG: {"purple"}, logging.NOTSET: set(), - } - LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-]?\d*s)") + } # type: Mapping[int, AbstractSet[str]] + LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-.]?\d*s)") - def __init__(self, terminalwriter, *args, **kwargs): - super(ColoredLevelFormatter, self).__init__(*args, **kwargs) - if six.PY2: - self._original_fmt = self._fmt - else: - self._original_fmt = self._style._fmt - self._level_to_fmt_mapping = {} + def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._original_fmt = self._style._fmt + self._level_to_fmt_mapping = {} # type: Dict[int, str] + assert self._fmt is not None levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt) if not levelname_fmt_match: return @@ -68,46 +83,111 @@ class ColoredLevelFormatter(logging.Formatter): colorized_formatted_levelname, self._fmt ) - def format(self, record): + def format(self, record: logging.LogRecord) -> str: fmt = self._level_to_fmt_mapping.get(record.levelno, self._original_fmt) - if six.PY2: - self._fmt = fmt - else: - self._style._fmt = fmt - return super(ColoredLevelFormatter, self).format(record) + self._style._fmt = fmt + return super().format(record) -if not six.PY2: - # Formatter classes don't support format styles in PY2 +class PercentStyleMultiline(logging.PercentStyle): + """A logging style with special support for multiline messages. - class PercentStyleMultiline(logging.PercentStyle): - """A logging style with special support for multiline messages. + If the message of a record consists of multiple lines, this style + formats the message as if each line were logged separately. + """ - If the message of a record consists of multiple lines, this style - formats the message as if each line were logged separately. + def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None: + super().__init__(fmt) + self._auto_indent = self._get_auto_indent(auto_indent) + + @staticmethod + def _update_message( + record_dict: Dict[str, object], message: str + ) -> Dict[str, object]: + tmp = record_dict.copy() + tmp["message"] = message + return tmp + + @staticmethod + def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int: + """Determine the current auto indentation setting. + + Specify auto indent behavior (on/off/fixed) by passing in + extra={"auto_indent": [value]} to the call to logging.log() or + using a --log-auto-indent [value] command line or the + log_auto_indent [value] config option. + + Default behavior is auto-indent off. + + Using the string "True" or "on" or the boolean True as the value + turns auto indent on, using the string "False" or "off" or the + boolean False or the int 0 turns it off, and specifying a + positive integer fixes the indentation position to the value + specified. + + Any other values for the option are invalid, and will silently be + converted to the default. + + :param None|bool|int|str auto_indent_option: + User specified option for indentation from command line, config + or extra kwarg. Accepts int, bool or str. str option accepts the + same range of values as boolean config options, as well as + positive integers represented in str form. + + :returns: + Indentation value, which can be + -1 (automatically determine indentation) or + 0 (auto-indent turned off) or + >0 (explicitly set indentation position). """ - @staticmethod - def _update_message(record_dict, message): - tmp = record_dict.copy() - tmp["message"] = message - return tmp + if auto_indent_option is None: + return 0 + elif isinstance(auto_indent_option, bool): + if auto_indent_option: + return -1 + else: + return 0 + elif isinstance(auto_indent_option, int): + return int(auto_indent_option) + elif isinstance(auto_indent_option, str): + try: + return int(auto_indent_option) + except ValueError: + pass + try: + if _strtobool(auto_indent_option): + return -1 + except ValueError: + return 0 + + return 0 + + def format(self, record: logging.LogRecord) -> str: + if "\n" in record.message: + if hasattr(record, "auto_indent"): + # Passed in from the "extra={}" kwarg on the call to logging.log(). + auto_indent = self._get_auto_indent(record.auto_indent) # type: ignore[attr-defined] + else: + auto_indent = self._auto_indent - def format(self, record): - if "\n" in record.message: + if auto_indent: lines = record.message.splitlines() formatted = self._fmt % self._update_message(record.__dict__, lines[0]) - # TODO optimize this by introducing an option that tells the - # logging framework that the indentation doesn't - # change. This allows to compute the indentation only once. - indentation = _remove_ansi_escape_sequences(formatted).find(lines[0]) + + if auto_indent < 0: + indentation = _remove_ansi_escape_sequences(formatted).find( + lines[0] + ) + else: + # Optimizes logging by allowing a fixed indentation. + indentation = auto_indent lines[0] = formatted return ("\n" + " " * indentation).join(lines) - else: - return self._fmt % record.__dict__ + return self._fmt % record.__dict__ -def get_option_ini(config, *names): +def get_option_ini(config: Config, *names: str): for name in names: ret = config.getoption(name) # 'default' arg won't work as expected if ret is None: @@ -116,7 +196,7 @@ def get_option_ini(config, *names): return ret -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: """Add options to control log capturing.""" group = parser.getgroup("logging") @@ -127,19 +207,15 @@ def pytest_addoption(parser): group.addoption(option, dest=dest, **kwargs) add_option_ini( - "--no-print-logs", - dest="log_print", - action="store_const", - const=False, - default=True, - type="bool", - help="disable printing caught logs on failed tests.", - ) - add_option_ini( "--log-level", dest="log_level", default=None, - help="logging level used by the logging module", + metavar="LEVEL", + help=( + "level of messages to catch/display.\n" + "Not set by default, so it depends on the root/parent log handler's" + ' effective level, where it is "WARNING" by default.' + ), ) add_option_ini( "--log-format", @@ -198,111 +274,128 @@ def pytest_addoption(parser): default=DEFAULT_LOG_DATE_FORMAT, help="log date format as used by the logging module.", ) + add_option_ini( + "--log-auto-indent", + dest="log_auto_indent", + default=None, + help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.", + ) -@contextmanager -def catching_logs(handler, formatter=None, level=None): +_HandlerType = TypeVar("_HandlerType", bound=logging.Handler) + + +# Not using @contextmanager for performance reasons. +class catching_logs: """Context manager that prepares the whole logging machinery properly.""" - root_logger = logging.getLogger() - - if formatter is not None: - handler.setFormatter(formatter) - if level is not None: - handler.setLevel(level) - - # Adding the same handler twice would confuse logging system. - # Just don't do that. - add_new_handler = handler not in root_logger.handlers - - if add_new_handler: - root_logger.addHandler(handler) - if level is not None: - orig_level = root_logger.level - root_logger.setLevel(min(orig_level, level)) - try: - yield handler - finally: - if level is not None: - root_logger.setLevel(orig_level) - if add_new_handler: - root_logger.removeHandler(handler) + + __slots__ = ("handler", "level", "orig_level") + + def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None: + self.handler = handler + self.level = level + + def __enter__(self): + root_logger = logging.getLogger() + if self.level is not None: + self.handler.setLevel(self.level) + root_logger.addHandler(self.handler) + if self.level is not None: + self.orig_level = root_logger.level + root_logger.setLevel(min(self.orig_level, self.level)) + return self.handler + + def __exit__(self, type, value, traceback): + root_logger = logging.getLogger() + if self.level is not None: + root_logger.setLevel(self.orig_level) + root_logger.removeHandler(self.handler) class LogCaptureHandler(logging.StreamHandler): """A logging handler that stores log records and the log text.""" - def __init__(self): - """Creates a new log handler.""" - logging.StreamHandler.__init__(self, py.io.TextIO()) - self.records = [] + stream = None # type: StringIO - def emit(self, record): + def __init__(self) -> None: + """Create a new log handler.""" + super().__init__(StringIO()) + self.records = [] # type: List[logging.LogRecord] + + def emit(self, record: logging.LogRecord) -> None: """Keep the log records in a list in addition to the log text.""" self.records.append(record) - logging.StreamHandler.emit(self, record) + super().emit(record) - def reset(self): + def reset(self) -> None: self.records = [] - self.stream = py.io.TextIO() + self.stream = StringIO() + + def handleError(self, record: logging.LogRecord) -> None: + if logging.raiseExceptions: + # Fail the test if the log message is bad (emit failed). + # The default behavior of logging is to print "Logging error" + # to stderr with the call stack and some extra details. + # pytest wants to make such mistakes visible during testing. + raise -class LogCaptureFixture(object): +@final +class LogCaptureFixture: """Provides access and control of log capturing.""" - def __init__(self, item): - """Creates a new funcarg.""" + def __init__(self, item: nodes.Node) -> None: self._item = item - # dict of log name -> log level - self._initial_log_levels = {} # Dict[str, int] + self._initial_handler_level = None # type: Optional[int] + # Dict of log name -> log level. + self._initial_logger_levels = {} # type: Dict[Optional[str], int] - def _finalize(self): - """Finalizes the fixture. + def _finalize(self) -> None: + """Finalize the fixture. This restores the log levels changed by :meth:`set_level`. """ - # restore log levels - for logger_name, level in self._initial_log_levels.items(): + # Restore log levels. + if self._initial_handler_level is not None: + self.handler.setLevel(self._initial_handler_level) + for logger_name, level in self._initial_logger_levels.items(): logger = logging.getLogger(logger_name) logger.setLevel(level) @property - def handler(self): - """ + def handler(self) -> LogCaptureHandler: + """Get the logging handler used by the fixture. + :rtype: LogCaptureHandler """ - return self._item.catch_log_handler + return self._item._store[caplog_handler_key] - def get_records(self, when): - """ - Get the logging records for one of the possible test phases. + def get_records(self, when: str) -> List[logging.LogRecord]: + """Get the logging records for one of the possible test phases. :param str when: Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown". + :returns: The list of captured records at the given stage. :rtype: List[logging.LogRecord] - :return: the list of captured records at the given stage .. versionadded:: 3.4 """ - handler = self._item.catch_log_handlers.get(when) - if handler: - return handler.records - else: - return [] + return self._item._store[caplog_records_key].get(when, []) @property - def text(self): - """Returns the formatted log text.""" + def text(self) -> str: + """The formatted log text.""" return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) @property - def records(self): - """Returns the list of log records.""" + def records(self) -> List[logging.LogRecord]: + """The list of log records.""" return self.handler.records @property - def record_tuples(self): - """Returns a list of a stripped down version of log records intended + def record_tuples(self) -> List[Tuple[str, int, str]]: + """A list of a stripped down version of log records intended for use in assertion comparison. The format of the tuple is: @@ -312,65 +405,76 @@ class LogCaptureFixture(object): return [(r.name, r.levelno, r.getMessage()) for r in self.records] @property - def messages(self): - """Returns a list of format-interpolated log messages. + def messages(self) -> List[str]: + """A list of format-interpolated log messages. - Unlike 'records', which contains the format string and parameters for interpolation, log messages in this list - are all interpolated. - Unlike 'text', which contains the output from the handler, log messages in this list are unadorned with - levels, timestamps, etc, making exact comparisons more reliable. + Unlike 'records', which contains the format string and parameters for + interpolation, log messages in this list are all interpolated. - Note that traceback or stack info (from :func:`logging.exception` or the `exc_info` or `stack_info` arguments - to the logging functions) is not included, as this is added by the formatter in the handler. + Unlike 'text', which contains the output from the handler, log + messages in this list are unadorned with levels, timestamps, etc, + making exact comparisons more reliable. + + Note that traceback or stack info (from :func:`logging.exception` or + the `exc_info` or `stack_info` arguments to the logging functions) is + not included, as this is added by the formatter in the handler. .. versionadded:: 3.7 """ return [r.getMessage() for r in self.records] - def clear(self): + def clear(self) -> None: """Reset the list of log records and the captured log text.""" self.handler.reset() - def set_level(self, level, logger=None): - """Sets the level for capturing of logs. The level will be restored to its previous value at the end of - the test. - - :param int level: the logger to level. - :param str logger: the logger to update the level. If not given, the root logger level is updated. + def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: + """Set the level of a logger for the duration of a test. .. versionchanged:: 3.4 - The levels of the loggers changed by this function will be restored to their initial values at the - end of the test. + The levels of the loggers changed by this function will be + restored to their initial values at the end of the test. + + :param int level: The level. + :param str logger: The logger to update. If not given, the root logger. """ - logger_name = logger - logger = logging.getLogger(logger_name) - # save the original log-level to restore it during teardown - self._initial_log_levels.setdefault(logger_name, logger.level) - logger.setLevel(level) + logger_obj = logging.getLogger(logger) + # Save the original log-level to restore it during teardown. + self._initial_logger_levels.setdefault(logger, logger_obj.level) + logger_obj.setLevel(level) + if self._initial_handler_level is None: + self._initial_handler_level = self.handler.level + self.handler.setLevel(level) @contextmanager - def at_level(self, level, logger=None): - """Context manager that sets the level for capturing of logs. After the end of the 'with' statement the - level is restored to its original value. - - :param int level: the logger to level. - :param str logger: the logger to update the level. If not given, the root logger level is updated. + def at_level( + self, level: int, logger: Optional[str] = None + ) -> Generator[None, None, None]: + """Context manager that sets the level for capturing of logs. After + the end of the 'with' statement the level is restored to its original + value. + + :param int level: The level. + :param str logger: The logger to update. If not given, the root logger. """ - logger = logging.getLogger(logger) - orig_level = logger.level - logger.setLevel(level) + logger_obj = logging.getLogger(logger) + orig_level = logger_obj.level + logger_obj.setLevel(level) + handler_orig_level = self.handler.level + self.handler.setLevel(level) try: yield finally: - logger.setLevel(orig_level) + logger_obj.setLevel(orig_level) + self.handler.setLevel(handler_orig_level) @pytest.fixture -def caplog(request): +def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]: """Access and control log capturing. Captured logs are available through the following properties/methods:: + * caplog.messages -> list of format-interpolated log messages * caplog.text -> string containing formatted log output * caplog.records -> list of logging.LogRecord instances * caplog.record_tuples -> list of (logger_name, level, message) tuples @@ -381,9 +485,7 @@ def caplog(request): result._finalize() -def get_actual_log_level(config, *setting_names): - """Return the actual logging level.""" - +def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]: for setting_name in setting_names: log_level = config.getoption(setting_name) if log_level is None: @@ -391,308 +493,297 @@ def get_actual_log_level(config, *setting_names): if log_level: break else: - return + return None - if isinstance(log_level, six.string_types): + if isinstance(log_level, str): log_level = log_level.upper() try: return int(getattr(logging, log_level, log_level)) - except ValueError: + except ValueError as e: # Python logging does not recognise this as a logging level raise pytest.UsageError( "'{}' is not recognized as a logging level name for " "'{}'. Please consider passing the " "logging level num instead.".format(log_level, setting_name) - ) + ) from e # run after terminalreporter/capturemanager are configured @pytest.hookimpl(trylast=True) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.pluginmanager.register(LoggingPlugin(config), "logging-plugin") -class LoggingPlugin(object): - """Attaches to the logging module and captures log messages for each test. - """ +class LoggingPlugin: + """Attaches to the logging module and captures log messages for each test.""" - def __init__(self, config): - """Creates a new plugin to capture log messages. + def __init__(self, config: Config) -> None: + """Create a new plugin to capture log messages. The formatter can be safely shared across all handlers so create a single one for the entire test session here. """ self._config = config - self.print_logs = get_option_ini(config, "log_print") + # Report logging. self.formatter = self._create_formatter( get_option_ini(config, "log_format"), get_option_ini(config, "log_date_format"), + get_option_ini(config, "log_auto_indent"), ) - self.log_level = get_actual_log_level(config, "log_level") - - self.log_file_level = get_actual_log_level(config, "log_file_level") - self.log_file_format = get_option_ini(config, "log_file_format", "log_format") - self.log_file_date_format = get_option_ini( + self.log_level = get_log_level_for_setting(config, "log_level") + self.caplog_handler = LogCaptureHandler() + self.caplog_handler.setFormatter(self.formatter) + self.report_handler = LogCaptureHandler() + self.report_handler.setFormatter(self.formatter) + + # File logging. + self.log_file_level = get_log_level_for_setting(config, "log_file_level") + log_file = get_option_ini(config, "log_file") or os.devnull + if log_file != os.devnull: + directory = os.path.dirname(os.path.abspath(log_file)) + if not os.path.isdir(directory): + os.makedirs(directory) + + self.log_file_handler = _FileHandler(log_file, mode="w", encoding="UTF-8") + log_file_format = get_option_ini(config, "log_file_format", "log_format") + log_file_date_format = get_option_ini( config, "log_file_date_format", "log_date_format" ) - self.log_file_formatter = logging.Formatter( - self.log_file_format, datefmt=self.log_file_date_format - ) - - log_file = get_option_ini(config, "log_file") - if log_file: - self.log_file_handler = logging.FileHandler( - log_file, mode="w", encoding="UTF-8" - ) - self.log_file_handler.setFormatter(self.log_file_formatter) - else: - self.log_file_handler = None - self.log_cli_handler = None - - self.live_logs_context = lambda: dummy_context_manager() - # Note that the lambda for the live_logs_context is needed because - # live_logs_context can otherwise not be entered multiple times due - # to limitations of contextlib.contextmanager. + log_file_formatter = logging.Formatter( + log_file_format, datefmt=log_file_date_format + ) + self.log_file_handler.setFormatter(log_file_formatter) + # CLI/live logging. + self.log_cli_level = get_log_level_for_setting( + config, "log_cli_level", "log_level" + ) if self._log_cli_enabled(): - self._setup_cli_logging() + terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") + capture_manager = config.pluginmanager.get_plugin("capturemanager") + # if capturemanager plugin is disabled, live logging still works. + self.log_cli_handler = _LiveLoggingStreamHandler( + terminal_reporter, capture_manager + ) # type: Union[_LiveLoggingStreamHandler, _LiveLoggingNullHandler] + else: + self.log_cli_handler = _LiveLoggingNullHandler() + log_cli_formatter = self._create_formatter( + get_option_ini(config, "log_cli_format", "log_format"), + get_option_ini(config, "log_cli_date_format", "log_date_format"), + get_option_ini(config, "log_auto_indent"), + ) + self.log_cli_handler.setFormatter(log_cli_formatter) - def _create_formatter(self, log_format, log_date_format): - # color option doesn't exist if terminal plugin is disabled + def _create_formatter(self, log_format, log_date_format, auto_indent): + # Color option doesn't exist if terminal plugin is disabled. color = getattr(self._config.option, "color", "no") if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search( log_format ): formatter = ColoredLevelFormatter( create_terminal_writer(self._config), log_format, log_date_format - ) + ) # type: logging.Formatter else: formatter = logging.Formatter(log_format, log_date_format) - if not six.PY2: - formatter._style = PercentStyleMultiline(formatter._style._fmt) - return formatter - - def _setup_cli_logging(self): - config = self._config - terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") - if terminal_reporter is None: - # terminal reporter is disabled e.g. by pytest-xdist. - return - - capture_manager = config.pluginmanager.get_plugin("capturemanager") - # if capturemanager plugin is disabled, live logging still works. - log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) - - log_cli_formatter = self._create_formatter( - get_option_ini(config, "log_cli_format", "log_format"), - get_option_ini(config, "log_cli_date_format", "log_date_format"), + formatter._style = PercentStyleMultiline( + formatter._style._fmt, auto_indent=auto_indent ) - log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level") - self.log_cli_handler = log_cli_handler - self.live_logs_context = lambda: catching_logs( - log_cli_handler, formatter=log_cli_formatter, level=log_cli_level - ) + return formatter + + def set_log_path(self, fname: str) -> None: + """Set the filename parameter for Logging.FileHandler(). - def set_log_path(self, fname): - """Public method, which can set filename parameter for - Logging.FileHandler(). Also creates parent directory if - it does not exist. + Creates parent directory if it does not exist. .. warning:: - Please considered as an experimental API. + This is an experimental API. """ - fname = Path(fname) + fpath = Path(fname) - if not fname.is_absolute(): - fname = Path(self._config.rootdir, fname) + if not fpath.is_absolute(): + fpath = self._config.rootpath / fpath - if not fname.parent.exists(): - fname.parent.mkdir(exist_ok=True, parents=True) + if not fpath.parent.exists(): + fpath.parent.mkdir(exist_ok=True, parents=True) - self.log_file_handler = logging.FileHandler( - str(fname), mode="w", encoding="UTF-8" - ) - self.log_file_handler.setFormatter(self.log_file_formatter) + stream = fpath.open(mode="w", encoding="UTF-8") + if sys.version_info >= (3, 7): + old_stream = self.log_file_handler.setStream(stream) + else: + old_stream = self.log_file_handler.stream + self.log_file_handler.acquire() + try: + self.log_file_handler.flush() + self.log_file_handler.stream = stream + finally: + self.log_file_handler.release() + if old_stream: + old_stream.close() def _log_cli_enabled(self): - """Return True if log_cli should be considered enabled, either explicitly - or because --log-cli-level was given in the command-line. - """ - return self._config.getoption( + """Return whether live logging is enabled.""" + enabled = self._config.getoption( "--log-cli-level" ) is not None or self._config.getini("log_cli") + if not enabled: + return False + + terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter") + if terminal_reporter is None: + # terminal reporter is disabled e.g. by pytest-xdist. + return False + + return True @pytest.hookimpl(hookwrapper=True, tryfirst=True) - def pytest_collection(self): - with self.live_logs_context(): - if self.log_cli_handler: - self.log_cli_handler.set_when("collection") - - if self.log_file_handler is not None: - with catching_logs(self.log_file_handler, level=self.log_file_level): - yield - else: - yield + def pytest_sessionstart(self) -> Generator[None, None, None]: + self.log_cli_handler.set_when("sessionstart") - @contextmanager - def _runtest_for(self, item, when): - with self._runtest_for_main(item, when): - if self.log_file_handler is not None: - with catching_logs(self.log_file_handler, level=self.log_file_level): - yield - else: + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): yield - @contextmanager - def _runtest_for_main(self, item, when): - """Implements the internals of pytest_runtest_xxx() hook.""" - with catching_logs( - LogCaptureHandler(), formatter=self.formatter, level=self.log_level - ) as log_handler: - if self.log_cli_handler: - self.log_cli_handler.set_when(when) - - if item is None: - yield # run the test - return - - if not hasattr(item, "catch_log_handlers"): - item.catch_log_handlers = {} - item.catch_log_handlers[when] = log_handler - item.catch_log_handler = log_handler - try: - yield # run test - finally: - if when == "teardown": - del item.catch_log_handler - del item.catch_log_handlers + @pytest.hookimpl(hookwrapper=True, tryfirst=True) + def pytest_collection(self) -> Generator[None, None, None]: + self.log_cli_handler.set_when("collection") - if self.print_logs: - # Add a captured log section to the report. - log = log_handler.stream.getvalue().strip() - item.add_report_section(when, "log", log) + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): + yield @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_setup(self, item): - with self._runtest_for(item, "setup"): + def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]: + if session.config.option.collectonly: yield + return - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_call(self, item): - with self._runtest_for(item, "call"): - yield + if self._log_cli_enabled() and self._config.getoption("verbose") < 1: + # The verbose flag is needed to avoid messy test progress output. + self._config.option.verbose = 1 + + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): + yield # Run all the tests. + + @pytest.hookimpl + def pytest_runtest_logstart(self) -> None: + self.log_cli_handler.reset() + self.log_cli_handler.set_when("start") + + @pytest.hookimpl + def pytest_runtest_logreport(self) -> None: + self.log_cli_handler.set_when("logreport") + + def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]: + """Implement the internals of the pytest_runtest_xxx() hooks.""" + with catching_logs( + self.caplog_handler, level=self.log_level, + ) as caplog_handler, catching_logs( + self.report_handler, level=self.log_level, + ) as report_handler: + caplog_handler.reset() + report_handler.reset() + item._store[caplog_records_key][when] = caplog_handler.records + item._store[caplog_handler_key] = caplog_handler - @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_teardown(self, item): - with self._runtest_for(item, "teardown"): yield + log = report_handler.stream.getvalue().strip() + item.add_report_section(when, "log", log) + @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_logstart(self): - if self.log_cli_handler: - self.log_cli_handler.reset() - with self._runtest_for(None, "start"): - yield + def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: + self.log_cli_handler.set_when("setup") + + empty = {} # type: Dict[str, List[logging.LogRecord]] + item._store[caplog_records_key] = empty + yield from self._runtest_for(item, "setup") @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_logfinish(self): - with self._runtest_for(None, "finish"): - yield + def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]: + self.log_cli_handler.set_when("call") + + yield from self._runtest_for(item, "call") @pytest.hookimpl(hookwrapper=True) - def pytest_runtest_logreport(self): - with self._runtest_for(None, "logreport"): - yield + def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]: + self.log_cli_handler.set_when("teardown") - @pytest.hookimpl(hookwrapper=True, tryfirst=True) - def pytest_sessionfinish(self): - with self.live_logs_context(): - if self.log_cli_handler: - self.log_cli_handler.set_when("sessionfinish") - if self.log_file_handler is not None: - try: - with catching_logs( - self.log_file_handler, level=self.log_file_level - ): - yield - finally: - # Close the FileHandler explicitly. - # (logging.shutdown might have lost the weakref?!) - self.log_file_handler.close() - else: - yield + yield from self._runtest_for(item, "teardown") + del item._store[caplog_records_key] + del item._store[caplog_handler_key] + + @pytest.hookimpl + def pytest_runtest_logfinish(self) -> None: + self.log_cli_handler.set_when("finish") @pytest.hookimpl(hookwrapper=True, tryfirst=True) - def pytest_sessionstart(self): - with self.live_logs_context(): - if self.log_cli_handler: - self.log_cli_handler.set_when("sessionstart") - if self.log_file_handler is not None: - with catching_logs(self.log_file_handler, level=self.log_file_level): - yield - else: + def pytest_sessionfinish(self) -> Generator[None, None, None]: + self.log_cli_handler.set_when("sessionfinish") + + with catching_logs(self.log_cli_handler, level=self.log_cli_level): + with catching_logs(self.log_file_handler, level=self.log_file_level): yield - @pytest.hookimpl(hookwrapper=True) - def pytest_runtestloop(self, session): - """Runs all collected test items.""" + @pytest.hookimpl + def pytest_unconfigure(self) -> None: + # Close the FileHandler explicitly. + # (logging.shutdown might have lost the weakref?!) + self.log_file_handler.close() - if session.config.option.collectonly: - yield - return - if self._log_cli_enabled() and self._config.getoption("verbose") < 1: - # setting verbose flag is needed to avoid messy test progress output - self._config.option.verbose = 1 +class _FileHandler(logging.FileHandler): + """A logging FileHandler with pytest tweaks.""" - with self.live_logs_context(): - if self.log_file_handler is not None: - with catching_logs(self.log_file_handler, level=self.log_file_level): - yield # run all the tests - else: - yield # run all the tests + def handleError(self, record: logging.LogRecord) -> None: + # Handled by LogCaptureHandler. + pass class _LiveLoggingStreamHandler(logging.StreamHandler): - """ - Custom StreamHandler used by the live logging feature: it will write a newline before the first log message - in each test. + """A logging StreamHandler used by the live logging feature: it will + write a newline before the first log message in each test. - During live logging we must also explicitly disable stdout/stderr capturing otherwise it will get captured - and won't appear in the terminal. + During live logging we must also explicitly disable stdout/stderr + capturing otherwise it will get captured and won't appear in the + terminal. """ - def __init__(self, terminal_reporter, capture_manager): - """ - :param _pytest.terminal.TerminalReporter terminal_reporter: - :param _pytest.capture.CaptureManager capture_manager: - """ - logging.StreamHandler.__init__(self, stream=terminal_reporter) + # Officially stream needs to be a IO[str], but TerminalReporter + # isn't. So force it. + stream = None # type: TerminalReporter # type: ignore + + def __init__( + self, + terminal_reporter: TerminalReporter, + capture_manager: Optional[CaptureManager], + ) -> None: + logging.StreamHandler.__init__(self, stream=terminal_reporter) # type: ignore[arg-type] self.capture_manager = capture_manager self.reset() self.set_when(None) self._test_outcome_written = False - def reset(self): - """Reset the handler; should be called before the start of each test""" + def reset(self) -> None: + """Reset the handler; should be called before the start of each test.""" self._first_record_emitted = False - def set_when(self, when): - """Prepares for the given test phase (setup/call/teardown)""" + def set_when(self, when: Optional[str]) -> None: + """Prepare for the given test phase (setup/call/teardown).""" self._when = when self._section_name_shown = False if when == "start": self._test_outcome_written = False - def emit(self, record): + def emit(self, record: logging.LogRecord) -> None: ctx_manager = ( self.capture_manager.global_and_fixture_disabled() if self.capture_manager - else dummy_context_manager() + else nullcontext() ) with ctx_manager: if not self._first_record_emitted: @@ -705,4 +796,22 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): if not self._section_name_shown and self._when: self.stream.section("live log " + self._when, sep="-", bold=True) self._section_name_shown = True - logging.StreamHandler.emit(self, record) + super().emit(record) + + def handleError(self, record: logging.LogRecord) -> None: + # Handled by LogCaptureHandler. + pass + + +class _LiveLoggingNullHandler(logging.NullHandler): + """A logging handler used when live logging is disabled.""" + + def reset(self) -> None: + pass + + def set_when(self, when: str) -> None: + pass + + def handleError(self, record: logging.LogRecord) -> None: + # Handled by LogCaptureHandler. + pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py index a9d310cb62d..ef106c46a43 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/main.py @@ -1,40 +1,54 @@ -# -*- coding: utf-8 -*- -""" core implementation of testing process: init, session, runtest loop. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import contextlib +"""Core implementation of the testing process: init, session, runtest loop.""" +import argparse import fnmatch import functools +import importlib import os -import pkgutil import sys -import warnings +from typing import Callable +from typing import Dict +from typing import FrozenSet +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Union import attr import py -import six import _pytest._code from _pytest import nodes +from _pytest.compat import final +from _pytest.compat import overload +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config from _pytest.config import directory_arg +from _pytest.config import ExitCode from _pytest.config import hookimpl +from _pytest.config import PytestPluginManager from _pytest.config import UsageError -from _pytest.deprecated import PYTEST_CONFIG_GLOBAL +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureManager from _pytest.outcomes import exit +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import Path +from _pytest.pathlib import visit +from _pytest.reports import CollectReport +from _pytest.reports import TestReport from _pytest.runner import collect_one_node +from _pytest.runner import SetupState + -# exitcodes for the command line -EXIT_OK = 0 -EXIT_TESTSFAILED = 1 -EXIT_INTERRUPTED = 2 -EXIT_INTERNALERROR = 3 -EXIT_USAGEERROR = 4 -EXIT_NOTESTSCOLLECTED = 5 +if TYPE_CHECKING: + from typing import Type + from typing_extensions import Literal -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addini( "norecursedirs", "directory patterns to avoid for recursion", @@ -56,7 +70,21 @@ def pytest_addoption(parser): dest="maxfail", const=1, help="exit instantly on first error or failed test.", - ), + ) + group = parser.getgroup("pytest-warnings") + group.addoption( + "-W", + "--pythonwarnings", + action="append", + help="set which warnings to report, see -W option of python itself.", + ) + parser.addini( + "filterwarnings", + type="linelist", + help="Each line specifies a pattern for " + "warnings.filterwarnings. " + "Processed after -W/--pythonwarnings.", + ) group._addoption( "--maxfail", metavar="num", @@ -67,6 +95,11 @@ def pytest_addoption(parser): help="exit after first num failures or errors.", ) group._addoption( + "--strict-config", + action="store_true", + help="any warnings encountered while parsing the `pytest` section of the configuration file raise errors.", + ) + group._addoption( "--strict-markers", "--strict", action="store_true", @@ -100,9 +133,10 @@ def pytest_addoption(parser): group.addoption( "--collectonly", "--collect-only", + "--co", action="store_true", help="only collect tests, don't execute them.", - ), + ) group.addoption( "--pyargs", action="store_true", @@ -124,10 +158,8 @@ def pytest_addoption(parser): "--deselect", action="append", metavar="nodeid_prefix", - help="deselect item during collection (multi-allowed).", + help="deselect item (via node id prefix) during collection (multi-allowed).", ) - # when changing this to --conf-cut-dir, config.py Conftest.setinitial - # needs upgrading as well group.addoption( "--confcutdir", dest="confcutdir", @@ -158,12 +190,21 @@ def pytest_addoption(parser): default=False, help="Don't ignore tests in a local virtualenv directory", ) + group.addoption( + "--import-mode", + default="prepend", + choices=["prepend", "append", "importlib"], + dest="importmode", + help="prepend/append to sys.path when importing test modules and conftest files, " + "default is to prepend.", + ) group = parser.getgroup("debugconfig", "test session debugging and configuration") group.addoption( "--basetemp", dest="basetemp", default=None, + type=validate_basetemp, metavar="dir", help=( "base temporary directory for this test run." @@ -172,30 +213,40 @@ def pytest_addoption(parser): ) -class _ConfigDeprecated(object): - def __init__(self, config): - self.__dict__["_config"] = config +def validate_basetemp(path: str) -> str: + # GH 7119 + msg = "basetemp must not be empty, the current working directory or any parent directory of it" - def __getattr__(self, attr): - warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2) - return getattr(self._config, attr) + # empty path + if not path: + raise argparse.ArgumentTypeError(msg) - def __setattr__(self, attr, val): - warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2) - return setattr(self._config, attr, val) + def is_ancestor(base: Path, query: Path) -> bool: + """Return whether query is an ancestor of base.""" + if base == query: + return True + for parent in base.parents: + if parent == query: + return True + return False - def __repr__(self): - return "{}({!r})".format(type(self).__name__, self._config) + # check if path is an ancestor of cwd + if is_ancestor(Path.cwd(), Path(path).absolute()): + raise argparse.ArgumentTypeError(msg) + # check symlinks for ancestors + if is_ancestor(Path.cwd().resolve(), Path(path).resolve()): + raise argparse.ArgumentTypeError(msg) -def pytest_configure(config): - __import__("pytest").config = _ConfigDeprecated(config) # compatibility + return path -def wrap_session(config, doit): - """Skeleton command line program""" - session = Session(config) - session.exitstatus = EXIT_OK +def wrap_session( + config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] +) -> Union[int, ExitCode]: + """Skeleton command line program.""" + session = Session.from_config(config) + session.exitstatus = ExitCode.OK initstate = 0 try: try: @@ -205,13 +256,13 @@ def wrap_session(config, doit): initstate = 2 session.exitstatus = doit(config, session) or 0 except UsageError: - session.exitstatus = EXIT_USAGEERROR + session.exitstatus = ExitCode.USAGE_ERROR raise except Failed: - session.exitstatus = EXIT_TESTSFAILED + session.exitstatus = ExitCode.TESTS_FAILED except (KeyboardInterrupt, exit.Exception): excinfo = _pytest._code.ExceptionInfo.from_current() - exitstatus = EXIT_INTERRUPTED + exitstatus = ExitCode.INTERRUPTED # type: Union[int, ExitCode] if isinstance(excinfo.value, exit.Exception): if excinfo.value.returncode is not None: exitstatus = excinfo.value.returncode @@ -221,47 +272,63 @@ def wrap_session(config, doit): ) config.hook.pytest_keyboard_interrupt(excinfo=excinfo) session.exitstatus = exitstatus - except: # noqa + except BaseException: + session.exitstatus = ExitCode.INTERNAL_ERROR excinfo = _pytest._code.ExceptionInfo.from_current() - config.notify_exception(excinfo, config.option) - session.exitstatus = EXIT_INTERNALERROR - if excinfo.errisinstance(SystemExit): - sys.stderr.write("mainloop: caught unexpected SystemExit!\n") + try: + config.notify_exception(excinfo, config.option) + except exit.Exception as exc: + if exc.returncode is not None: + session.exitstatus = exc.returncode + sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc)) + else: + if isinstance(excinfo.value, SystemExit): + sys.stderr.write("mainloop: caught unexpected SystemExit!\n") finally: - excinfo = None # Explicitly break reference cycle. + # Explicitly break reference cycle. + excinfo = None # type: ignore session.startdir.chdir() if initstate >= 2: - config.hook.pytest_sessionfinish( - session=session, exitstatus=session.exitstatus - ) + try: + config.hook.pytest_sessionfinish( + session=session, exitstatus=session.exitstatus + ) + except exit.Exception as exc: + if exc.returncode is not None: + session.exitstatus = exc.returncode + sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc)) config._ensure_unconfigure() return session.exitstatus -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]: return wrap_session(config, _main) -def _main(config, session): - """ default command line protocol for initialization, session, - running tests and reporting. """ +def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: + """Default command line protocol for initialization, session, + running tests and reporting.""" config.hook.pytest_collection(session=session) config.hook.pytest_runtestloop(session=session) if session.testsfailed: - return EXIT_TESTSFAILED + return ExitCode.TESTS_FAILED elif session.testscollected == 0: - return EXIT_NOTESTSCOLLECTED + return ExitCode.NO_TESTS_COLLECTED + return None -def pytest_collection(session): - return session.perform_collect() +def pytest_collection(session: "Session") -> None: + session.perform_collect() -def pytest_runtestloop(session): +def pytest_runtestloop(session: "Session") -> bool: if session.testsfailed and not session.config.option.continue_on_collection_errors: - raise session.Interrupted("%d errors during collection" % session.testsfailed) + raise session.Interrupted( + "%d error%s during collection" + % (session.testsfailed, "s" if session.testsfailed != 1 else "") + ) if session.config.option.collectonly: return True @@ -276,9 +343,9 @@ def pytest_runtestloop(session): return True -def _in_venv(path): - """Attempts to detect if ``path`` is the root of a Virtual Environment by - checking for the existence of the appropriate activate script""" +def _in_venv(path: py.path.local) -> bool: + """Attempt to detect if ``path`` is the root of a Virtual Environment by + checking for the existence of the appropriate activate script.""" bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin") if not bindir.isdir(): return False @@ -293,7 +360,7 @@ def _in_venv(path): return any([fname.basename in activates for fname in bindir.listdir()]) -def pytest_ignore_collect(path, config): +def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]: ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath()) ignore_paths = ignore_paths or [] excludeopt = config.getoption("ignore") @@ -311,20 +378,16 @@ def pytest_ignore_collect(path, config): if excludeglobopt: ignore_globs.extend([py.path.local(x) for x in excludeglobopt]) - if any( - fnmatch.fnmatch(six.text_type(path), six.text_type(glob)) - for glob in ignore_globs - ): + if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs): return True allow_in_venv = config.getoption("collect_in_virtualenv") if not allow_in_venv and _in_venv(path): return True + return None - return False - -def pytest_collection_modifyitems(items, config): +def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: return @@ -342,107 +405,71 @@ def pytest_collection_modifyitems(items, config): items[:] = remaining -@contextlib.contextmanager -def _patched_find_module(): - """Patch bug in pkgutil.ImpImporter.find_module - - When using pkgutil.find_loader on python<3.4 it removes symlinks - from the path due to a call to os.path.realpath. This is not consistent - with actually doing the import (in these versions, pkgutil and __import__ - did not share the same underlying code). This can break conftest - discovery for pytest where symlinks are involved. - - The only supported python<3.4 by pytest is python 2.7. - """ - if six.PY2: # python 3.4+ uses importlib instead - - def find_module_patched(self, fullname, path=None): - # Note: we ignore 'path' argument since it is only used via meta_path - subname = fullname.split(".")[-1] - if subname != fullname and self.path is None: - return None - if self.path is None: - path = None - else: - # original: path = [os.path.realpath(self.path)] - path = [self.path] - try: - file, filename, etc = pkgutil.imp.find_module(subname, path) - except ImportError: - return None - return pkgutil.ImpLoader(fullname, file, filename, etc) - - old_find_module = pkgutil.ImpImporter.find_module - pkgutil.ImpImporter.find_module = find_module_patched - try: - yield - finally: - pkgutil.ImpImporter.find_module = old_find_module - else: - yield - - -class FSHookProxy(object): - def __init__(self, fspath, pm, remove_mods): - self.fspath = fspath +class FSHookProxy: + def __init__(self, pm: PytestPluginManager, remove_mods) -> None: self.pm = pm self.remove_mods = remove_mods - def __getattr__(self, name): + def __getattr__(self, name: str): x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods) self.__dict__[name] = x return x -class NoMatch(Exception): - """ raised if matching cannot locate a matching names. """ - - class Interrupted(KeyboardInterrupt): - """ signals an interrupted test run. """ + """Signals that the test run was interrupted.""" - __module__ = "builtins" # for py3 + __module__ = "builtins" # For py3. class Failed(Exception): - """ signals a stop as failed test run. """ + """Signals a stop as failed test run.""" @attr.s -class _bestrelpath_cache(dict): - path = attr.ib() +class _bestrelpath_cache(Dict[Path, str]): + path = attr.ib(type=Path) - def __missing__(self, path): - r = self.path.bestrelpath(path) + def __missing__(self, path: Path) -> str: + r = bestrelpath(self.path, path) self[path] = r return r +@final class Session(nodes.FSCollector): Interrupted = Interrupted Failed = Failed - - def __init__(self, config): - nodes.FSCollector.__init__( - self, config.rootdir, parent=None, config=config, session=self, nodeid="" + # Set on the session by runner.pytest_sessionstart. + _setupstate = None # type: SetupState + # Set on the session by fixtures.pytest_sessionstart. + _fixturemanager = None # type: FixtureManager + exitstatus = None # type: Union[int, ExitCode] + + def __init__(self, config: Config) -> None: + super().__init__( + config.rootdir, parent=None, config=config, session=self, nodeid="" ) self.testsfailed = 0 self.testscollected = 0 - self.shouldstop = False - self.shouldfail = False + self.shouldstop = False # type: Union[bool, str] + self.shouldfail = False # type: Union[bool, str] self.trace = config.trace.root.get("collection") - self._norecursepatterns = config.getini("norecursedirs") self.startdir = config.invocation_dir - self._initialpaths = frozenset() - # Keep track of any collected nodes in here, so we don't duplicate fixtures - self._node_cache = {} - self._bestrelpathcache = _bestrelpath_cache(config.rootdir) - # Dirnames of pkgs with dunder-init files. - self._pkg_roots = {} + self._initialpaths = frozenset() # type: FrozenSet[py.path.local] + + self._bestrelpathcache = _bestrelpath_cache( + config.rootpath + ) # type: Dict[Path, str] self.config.pluginmanager.register(self, name="session") - def __repr__(self): + @classmethod + def from_config(cls, config: Config) -> "Session": + session = cls._create(config) # type: Session + return session + + def __repr__(self) -> str: return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % ( self.__class__.__name__, self.name, @@ -451,19 +478,21 @@ class Session(nodes.FSCollector): self.testscollected, ) - def _node_location_to_relpath(self, node_path): - # bestrelpath is a quite slow function + def _node_location_to_relpath(self, node_path: Path) -> str: + # bestrelpath is a quite slow function. return self._bestrelpathcache[node_path] @hookimpl(tryfirst=True) - def pytest_collectstart(self): + def pytest_collectstart(self) -> None: if self.shouldfail: raise self.Failed(self.shouldfail) if self.shouldstop: raise self.Interrupted(self.shouldstop) @hookimpl(tryfirst=True) - def pytest_runtest_logreport(self, report): + def pytest_runtest_logreport( + self, report: Union[TestReport, CollectReport] + ) -> None: if report.failed and not hasattr(report, "wasxfail"): self.testsfailed += 1 maxfail = self.config.getvalue("maxfail") @@ -472,172 +501,44 @@ class Session(nodes.FSCollector): pytest_collectreport = pytest_runtest_logreport - def isinitpath(self, path): + def isinitpath(self, path: py.path.local) -> bool: return path in self._initialpaths - def gethookproxy(self, fspath): - # check if we have the common case of running - # hooks with all conftest.py files + def gethookproxy(self, fspath: py.path.local): + # Check if we have the common case of running + # hooks with all conftest.py files. pm = self.config.pluginmanager - my_conftestmodules = pm._getconftestmodules(fspath) + my_conftestmodules = pm._getconftestmodules( + fspath, self.config.getoption("importmode") + ) remove_mods = pm._conftest_plugins.difference(my_conftestmodules) if remove_mods: - # one or more conftests are not in use at this fspath - proxy = FSHookProxy(fspath, pm, remove_mods) + # One or more conftests are not in use at this fspath. + proxy = FSHookProxy(pm, remove_mods) else: - # all plugis are active for this fspath + # All plugins are active for this fspath. proxy = self.config.hook return proxy - def perform_collect(self, args=None, genitems=True): - hook = self.config.hook - try: - items = self._perform_collect(args, genitems) - self.config.pluginmanager.check_pending() - hook.pytest_collection_modifyitems( - session=self, config=self.config, items=items - ) - finally: - hook.pytest_collection_finish(session=self) - self.testscollected = len(items) - return items - - def _perform_collect(self, args, genitems): - if args is None: - args = self.config.args - self.trace("perform_collect", self, args) - self.trace.root.indent += 1 - self._notfound = [] - initialpaths = [] - self._initialparts = [] - self.items = items = [] - for arg in args: - parts = self._parsearg(arg) - self._initialparts.append(parts) - initialpaths.append(parts[0]) - self._initialpaths = frozenset(initialpaths) - rep = collect_one_node(self) - self.ihook.pytest_collectreport(report=rep) - self.trace.root.indent -= 1 - if self._notfound: - errors = [] - for arg, exc in self._notfound: - line = "(no name %r in any of %r)" % (arg, exc.args[0]) - errors.append("not found: %s\n%s" % (arg, line)) - # XXX: test this - raise UsageError(*errors) - if not genitems: - return rep.result - else: - if rep.passed: - for node in rep.result: - self.items.extend(self.genitems(node)) - return items - - def collect(self): - for initialpart in self._initialparts: - arg = "::".join(map(str, initialpart)) - self.trace("processing argument", arg) - self.trace.root.indent += 1 - try: - for x in self._collect(arg): - yield x - except NoMatch: - # we are inside a make_report hook so - # we cannot directly pass through the exception - self._notfound.append((arg, sys.exc_info()[1])) - - self.trace.root.indent -= 1 - - def _collect(self, arg): - from _pytest.python import Package - - names = self._parsearg(arg) - argpath = names.pop(0) - - # Start with a Session root, and delve to argpath item (dir or file) - # and stack all Packages found on the way. - # No point in finding packages when collecting doctests - if not self.config.getoption("doctestmodules", False): - pm = self.config.pluginmanager - for parent in reversed(argpath.parts()): - if pm._confcutdir and pm._confcutdir.relto(parent): - break - - if parent.isdir(): - pkginit = parent.join("__init__.py") - if pkginit.isfile(): - if pkginit not in self._node_cache: - col = self._collectfile(pkginit, handle_dupes=False) - if col: - if isinstance(col[0], Package): - self._pkg_roots[parent] = col[0] - # always store a list in the cache, matchnodes expects it - self._node_cache[col[0].fspath] = [col[0]] - - # If it's a directory argument, recurse and look for any Subpackages. - # Let the Package collector deal with subnodes, don't collect here. - if argpath.check(dir=1): - assert not names, "invalid arg %r" % (arg,) - - seen_dirs = set() - for path in argpath.visit( - fil=self._visit_filter, rec=self._recurse, bf=True, sort=True - ): - dirpath = path.dirpath() - if dirpath not in seen_dirs: - # Collect packages first. - seen_dirs.add(dirpath) - pkginit = dirpath.join("__init__.py") - if pkginit.exists(): - for x in self._collectfile(pkginit): - yield x - if isinstance(x, Package): - self._pkg_roots[dirpath] = x - if dirpath in self._pkg_roots: - # Do not collect packages here. - continue - - for x in self._collectfile(path): - key = (type(x), x.fspath) - if key in self._node_cache: - yield self._node_cache[key] - else: - self._node_cache[key] = x - yield x - else: - assert argpath.check(file=1) + def _recurse(self, direntry: "os.DirEntry[str]") -> bool: + if direntry.name == "__pycache__": + return False + path = py.path.local(direntry.path) + ihook = self.gethookproxy(path.dirpath()) + if ihook.pytest_ignore_collect(path=path, config=self.config): + return False + norecursepatterns = self.config.getini("norecursedirs") + if any(path.check(fnmatch=pat) for pat in norecursepatterns): + return False + return True - if argpath in self._node_cache: - col = self._node_cache[argpath] - else: - collect_root = self._pkg_roots.get(argpath.dirname, self) - col = collect_root._collectfile(argpath, handle_dupes=False) - if col: - self._node_cache[argpath] = col - m = self.matchnodes(col, names) - # If __init__.py was the only file requested, then the matched node will be - # the corresponding Package, and the first yielded item will be the __init__ - # Module itself, so just use that. If this special case isn't taken, then all - # the files in the package will be yielded. - if argpath.basename == "__init__.py": - try: - yield next(m[0].collect()) - except StopIteration: - # The package collects nothing with only an __init__.py - # file in it, which gets ignored by the default - # "python_files" option. - pass - return - for y in m: - yield y - - def _collectfile(self, path, handle_dupes=True): - assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % ( - path, - path.isdir(), - path.exists(), - path.islink(), + def _collectfile( + self, path: py.path.local, handle_dupes: bool = True + ) -> Sequence[nodes.Collector]: + assert ( + path.isfile() + ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( + path, path.isdir(), path.exists(), path.islink() ) ihook = self.gethookproxy(path) if not self.isinitpath(path): @@ -653,119 +554,243 @@ class Session(nodes.FSCollector): else: duplicate_paths.add(path) - return ihook.pytest_collect_file(path=path, parent=self) - - def _recurse(self, dirpath): - if dirpath.basename == "__pycache__": - return False - ihook = self.gethookproxy(dirpath.dirpath()) - if ihook.pytest_ignore_collect(path=dirpath, config=self.config): - return False - for pat in self._norecursepatterns: - if dirpath.check(fnmatch=pat): - return False - ihook = self.gethookproxy(dirpath) - ihook.pytest_collect_directory(path=dirpath, parent=self) - return True - - if six.PY2: + return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return] + + @overload + def perform_collect( + self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... + ) -> Sequence[nodes.Item]: + ... + + @overload # noqa: F811 + def perform_collect( # noqa: F811 + self, args: Optional[Sequence[str]] = ..., genitems: bool = ... + ) -> Sequence[Union[nodes.Item, nodes.Collector]]: + ... + + def perform_collect( # noqa: F811 + self, args: Optional[Sequence[str]] = None, genitems: bool = True + ) -> Sequence[Union[nodes.Item, nodes.Collector]]: + """Perform the collection phase for this session. + + This is called by the default + :func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook + implementation; see the documentation of this hook for more details. + For testing purposes, it may also be called directly on a fresh + ``Session``. + + This function normally recursively expands any collectors collected + from the session to their items, and only items are returned. For + testing purposes, this may be suppressed by passing ``genitems=False``, + in which case the return value contains these collectors unexpanded, + and ``session.items`` is empty. + """ + if args is None: + args = self.config.args - @staticmethod - def _visit_filter(f): - return f.check(file=1) and not f.strpath.endswith("*.pyc") + self.trace("perform_collect", self, args) + self.trace.root.indent += 1 - else: + self._notfound = [] # type: List[Tuple[str, Sequence[nodes.Collector]]] + self._initial_parts = [] # type: List[Tuple[py.path.local, List[str]]] + self.items = [] # type: List[nodes.Item] - @staticmethod - def _visit_filter(f): - return f.check(file=1) + hook = self.config.hook - def _tryconvertpyarg(self, x): - """Convert a dotted module name to path.""" + items = self.items # type: Sequence[Union[nodes.Item, nodes.Collector]] try: - with _patched_find_module(): - loader = pkgutil.find_loader(x) - except ImportError: - return x - if loader is None: - return x - # This method is sometimes invoked when AssertionRewritingHook, which - # does not define a get_filename method, is already in place: - try: - with _patched_find_module(): - path = loader.get_filename(x) - except AttributeError: - # Retrieve path from AssertionRewritingHook: - path = loader.modules[x][0].co_filename - if loader.is_package(x): - path = os.path.dirname(path) - return path - - def _parsearg(self, arg): - """ return (fspath, names) tuple after checking the file exists. """ - parts = str(arg).split("::") - if self.config.option.pyargs: - parts[0] = self._tryconvertpyarg(parts[0]) - relpath = parts[0].replace("/", os.sep) - path = self.config.invocation_dir.join(relpath, abs=True) - if not path.check(): - if self.config.option.pyargs: - raise UsageError( - "file or package not found: " + arg + " (missing __init__.py?)" + initialpaths = [] # type: List[py.path.local] + for arg in args: + fspath, parts = resolve_collection_argument( + self.config.invocation_params.dir, + arg, + as_pypath=self.config.option.pyargs, ) - raise UsageError("file not found: " + arg) - parts[0] = path.realpath() - return parts - - def matchnodes(self, matching, names): - self.trace("matchnodes", matching, names) - self.trace.root.indent += 1 - nodes = self._matchnodes(matching, names) - num = len(nodes) - self.trace("matchnodes finished -> ", num, "nodes") - self.trace.root.indent -= 1 - if num == 0: - raise NoMatch(matching, names[:1]) - return nodes - - def _matchnodes(self, matching, names): - if not matching or not names: - return matching - name = names[0] - assert name - nextnames = names[1:] - resultnodes = [] - for node in matching: - if isinstance(node, nodes.Item): - if not names: - resultnodes.append(node) - continue - assert isinstance(node, nodes.Collector) - key = (type(node), node.nodeid) - if key in self._node_cache: - rep = self._node_cache[key] + self._initial_parts.append((fspath, parts)) + initialpaths.append(fspath) + self._initialpaths = frozenset(initialpaths) + rep = collect_one_node(self) + self.ihook.pytest_collectreport(report=rep) + self.trace.root.indent -= 1 + if self._notfound: + errors = [] + for arg, cols in self._notfound: + line = "(no name {!r} in any of {!r})".format(arg, cols) + errors.append("not found: {}\n{}".format(arg, line)) + raise UsageError(*errors) + if not genitems: + items = rep.result else: - rep = collect_one_node(node) - self._node_cache[key] = rep - if rep.passed: - has_matched = False - for x in rep.result: - # TODO: remove parametrized workaround once collection structure contains parametrization - if x.name == name or x.name.split("[")[0] == name: - resultnodes.extend(self.matchnodes([x], nextnames)) - has_matched = True - # XXX accept IDs that don't have "()" for class instances - if not has_matched and len(rep.result) == 1 and x.name == "()": - nextnames.insert(0, name) - resultnodes.extend(self.matchnodes([x], nextnames)) + if rep.passed: + for node in rep.result: + self.items.extend(self.genitems(node)) + + self.config.pluginmanager.check_pending() + hook.pytest_collection_modifyitems( + session=self, config=self.config, items=items + ) + finally: + hook.pytest_collection_finish(session=self) + + self.testscollected = len(items) + return items + + def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: + from _pytest.python import Package + + # Keep track of any collected nodes in here, so we don't duplicate fixtures. + node_cache1 = {} # type: Dict[py.path.local, Sequence[nodes.Collector]] + node_cache2 = ( + {} + ) # type: Dict[Tuple[Type[nodes.Collector], py.path.local], nodes.Collector] + + # Keep track of any collected collectors in matchnodes paths, so they + # are not collected more than once. + matchnodes_cache = ( + {} + ) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport] + + # Dirnames of pkgs with dunder-init files. + pkg_roots = {} # type: Dict[str, Package] + + for argpath, names in self._initial_parts: + self.trace("processing argument", (argpath, names)) + self.trace.root.indent += 1 + + # Start with a Session root, and delve to argpath item (dir or file) + # and stack all Packages found on the way. + # No point in finding packages when collecting doctests. + if not self.config.getoption("doctestmodules", False): + pm = self.config.pluginmanager + for parent in reversed(argpath.parts()): + if pm._confcutdir and pm._confcutdir.relto(parent): + break + + if parent.isdir(): + pkginit = parent.join("__init__.py") + if pkginit.isfile() and pkginit not in node_cache1: + col = self._collectfile(pkginit, handle_dupes=False) + if col: + if isinstance(col[0], Package): + pkg_roots[str(parent)] = col[0] + node_cache1[col[0].fspath] = [col[0]] + + # If it's a directory argument, recurse and look for any Subpackages. + # Let the Package collector deal with subnodes, don't collect here. + if argpath.check(dir=1): + assert not names, "invalid arg {!r}".format((argpath, names)) + + seen_dirs = set() # type: Set[py.path.local] + for direntry in visit(str(argpath), self._recurse): + if not direntry.is_file(): + continue + + path = py.path.local(direntry.path) + dirpath = path.dirpath() + + if dirpath not in seen_dirs: + # Collect packages first. + seen_dirs.add(dirpath) + pkginit = dirpath.join("__init__.py") + if pkginit.exists(): + for x in self._collectfile(pkginit): + yield x + if isinstance(x, Package): + pkg_roots[str(dirpath)] = x + if str(dirpath) in pkg_roots: + # Do not collect packages here. + continue + + for x in self._collectfile(path): + key = (type(x), x.fspath) + if key in node_cache2: + yield node_cache2[key] + else: + node_cache2[key] = x + yield x else: - # report collection failures here to avoid failing to run some test - # specified in the command line because the module could not be - # imported (#134) - node.ihook.pytest_collectreport(report=rep) - return resultnodes + assert argpath.check(file=1) + + if argpath in node_cache1: + col = node_cache1[argpath] + else: + collect_root = pkg_roots.get(argpath.dirname, self) + col = collect_root._collectfile(argpath, handle_dupes=False) + if col: + node_cache1[argpath] = col + + matching = [] + work = [ + (col, names) + ] # type: List[Tuple[Sequence[Union[nodes.Item, nodes.Collector]], Sequence[str]]] + while work: + self.trace("matchnodes", col, names) + self.trace.root.indent += 1 + + matchnodes, matchnames = work.pop() + for node in matchnodes: + if not matchnames: + matching.append(node) + continue + if not isinstance(node, nodes.Collector): + continue + key = (type(node), node.nodeid) + if key in matchnodes_cache: + rep = matchnodes_cache[key] + else: + rep = collect_one_node(node) + matchnodes_cache[key] = rep + if rep.passed: + submatchnodes = [] + for r in rep.result: + # TODO: Remove parametrized workaround once collection structure contains + # parametrization. + if ( + r.name == matchnames[0] + or r.name.split("[")[0] == matchnames[0] + ): + submatchnodes.append(r) + if submatchnodes: + work.append((submatchnodes, matchnames[1:])) + # XXX Accept IDs that don't have "()" for class instances. + elif len(rep.result) == 1 and rep.result[0].name == "()": + work.append((rep.result, matchnames)) + else: + # Report collection failures here to avoid failing to run some test + # specified in the command line because the module could not be + # imported (#134). + node.ihook.pytest_collectreport(report=rep) + + self.trace("matchnodes finished -> ", len(matching), "nodes") + self.trace.root.indent -= 1 + + if not matching: + report_arg = "::".join((str(argpath), *names)) + self._notfound.append((report_arg, col)) + continue + + # If __init__.py was the only file requested, then the matched node will be + # the corresponding Package, and the first yielded item will be the __init__ + # Module itself, so just use that. If this special case isn't taken, then all + # the files in the package will be yielded. + if argpath.basename == "__init__.py": + assert isinstance(matching[0], nodes.Collector) + try: + yield next(iter(matching[0].collect())) + except StopIteration: + # The package collects nothing with only an __init__.py + # file in it, which gets ignored by the default + # "python_files" option. + pass + continue + + yield from matching + + self.trace.root.indent -= 1 - def genitems(self, node): + def genitems( + self, node: Union[nodes.Item, nodes.Collector] + ) -> Iterator[nodes.Item]: self.trace("genitems", node) if isinstance(node, nodes.Item): node.ihook.pytest_itemcollected(item=node) @@ -775,6 +800,69 @@ class Session(nodes.FSCollector): rep = collect_one_node(node) if rep.passed: for subnode in rep.result: - for x in self.genitems(subnode): - yield x + yield from self.genitems(subnode) node.ihook.pytest_collectreport(report=rep) + + +def search_pypath(module_name: str) -> str: + """Search sys.path for the given a dotted module name, and return its file system path.""" + try: + spec = importlib.util.find_spec(module_name) + # AttributeError: looks like package module, but actually filename + # ImportError: module does not exist + # ValueError: not a module name + except (AttributeError, ImportError, ValueError): + return module_name + if spec is None or spec.origin is None or spec.origin == "namespace": + return module_name + elif spec.submodule_search_locations: + return os.path.dirname(spec.origin) + else: + return spec.origin + + +def resolve_collection_argument( + invocation_path: Path, arg: str, *, as_pypath: bool = False +) -> Tuple[py.path.local, List[str]]: + """Parse path arguments optionally containing selection parts and return (fspath, names). + + Command-line arguments can point to files and/or directories, and optionally contain + parts for specific tests selection, for example: + + "pkg/tests/test_foo.py::TestClass::test_foo" + + This function ensures the path exists, and returns a tuple: + + (py.path.path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"]) + + When as_pypath is True, expects that the command-line argument actually contains + module paths instead of file-system paths: + + "pkg.tests.test_foo::TestClass::test_foo" + + In which case we search sys.path for a matching module, and then return the *path* to the + found module. + + If the path doesn't exist, raise UsageError. + If the path is a directory and selection parts are present, raise UsageError. + """ + strpath, *parts = str(arg).split("::") + if as_pypath: + strpath = search_pypath(strpath) + fspath = invocation_path / strpath + fspath = absolutepath(fspath) + if not fspath.exists(): + msg = ( + "module or package not found: {arg} (missing __init__.py?)" + if as_pypath + else "file or directory not found: {arg}" + ) + raise UsageError(msg.format(arg=arg)) + if parts and fspath.is_dir(): + msg = ( + "package argument cannot contain :: selection parts: {arg}" + if as_pypath + else "directory argument cannot contain :: selection parts: {arg}" + ) + raise UsageError(msg.format(arg=arg)) + return py.path.local(str(fspath)), parts diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py index 6bc22fe27da..6a9b262307e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/__init__.py @@ -1,11 +1,15 @@ -# -*- coding: utf-8 -*- -""" generic mechanism for marking and selecting python functions. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from .legacy import matchkeyword -from .legacy import matchmark +"""Generic mechanism for marking and selecting python functions.""" +import typing +import warnings +from typing import AbstractSet +from typing import List +from typing import Optional +from typing import Union + +import attr + +from .expression import Expression +from .expression import ParseError from .structures import EMPTY_PARAMETERSET_OPTION from .structures import get_empty_parameterset_mark from .structures import Mark @@ -13,32 +17,58 @@ from .structures import MARK_GEN from .structures import MarkDecorator from .structures import MarkGenerator from .structures import ParameterSet +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import hookimpl from _pytest.config import UsageError +from _pytest.config.argparsing import Parser +from _pytest.deprecated import MINUS_K_COLON +from _pytest.deprecated import MINUS_K_DASH +from _pytest.store import StoreKey -__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"] +if TYPE_CHECKING: + from _pytest.nodes import Item -def param(*values, **kw): +__all__ = [ + "MARK_GEN", + "Mark", + "MarkDecorator", + "MarkGenerator", + "ParameterSet", + "get_empty_parameterset_mark", +] + + +old_mark_config_key = StoreKey[Optional[Config]]() + + +def param( + *values: object, + marks: "Union[MarkDecorator, typing.Collection[Union[MarkDecorator, Mark]]]" = (), + id: Optional[str] = None +) -> ParameterSet: """Specify a parameter in `pytest.mark.parametrize`_ calls or :ref:`parametrized fixtures <fixture-parametrize-marks>`. .. code-block:: python - @pytest.mark.parametrize("test_input,expected", [ - ("3+5", 8), - pytest.param("6*9", 42, marks=pytest.mark.xfail), - ]) + @pytest.mark.parametrize( + "test_input,expected", + [("3+5", 8), pytest.param("6*9", 42, marks=pytest.mark.xfail),], + ) def test_eval(test_input, expected): assert eval(test_input) == expected - :param values: variable args of the values of the parameter set, in order. - :keyword marks: a single mark or a list of marks to be applied to this parameter set. - :keyword str id: the id to attribute to this parameter set. + :param values: Variable args of the values of the parameter set, in order. + :keyword marks: A single mark or a list of marks to be applied to this parameter set. + :keyword str id: The id to attribute to this parameter set. """ - return ParameterSet.param(*values, **kw) + return ParameterSet.param(*values, marks=marks, id=id) -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group._addoption( "-k", @@ -56,7 +86,8 @@ def pytest_addoption(parser): "-k 'not test_method and not test_other' will eliminate the matches. " "Additionally keywords are matched to classes and functions " "containing extra names in their 'extra_keyword_matches' set, " - "as well as functions which have names assigned directly to them.", + "as well as functions which have names assigned directly to them. " + "The matching is case-insensitive.", ) group._addoption( @@ -65,8 +96,8 @@ def pytest_addoption(parser): dest="markexpr", default="", metavar="MARKEXPR", - help="only run tests matching given mark expression. " - "example: -m 'mark1 and not mark2'.", + help="only run tests matching given mark expression.\n" + "For example: -m 'mark1 and not mark2'.", ) group.addoption( @@ -79,7 +110,8 @@ def pytest_addoption(parser): parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets") -def pytest_cmdline_main(config): +@hookimpl(tryfirst=True) +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: import _pytest.config if config.option.markers: @@ -95,26 +127,87 @@ def pytest_cmdline_main(config): config._ensure_unconfigure() return 0 + return None + + +@attr.s(slots=True) +class KeywordMatcher: + """A matcher for keywords. + + Given a list of names, matches any substring of one of these names. The + string inclusion check is case-insensitive. + + Will match on the name of colitem, including the names of its parents. + Only matches names of items which are either a :class:`Class` or a + :class:`Function`. + + Additionally, matches on names in the 'extra_keyword_matches' set of + any item, as well as names directly assigned to test functions. + """ + + _names = attr.ib(type=AbstractSet[str]) + + @classmethod + def from_item(cls, item: "Item") -> "KeywordMatcher": + mapped_names = set() + + # Add the names of the current item and any parent items. + import pytest + + for node in item.listchain(): + if not isinstance(node, (pytest.Instance, pytest.Session)): + mapped_names.add(node.name) + + # Add the names added as extra keywords to current or parent items. + mapped_names.update(item.listextrakeywords()) -pytest_cmdline_main.tryfirst = True + # Add the names attached to the current function through direct assignment. + function_obj = getattr(item, "function", None) + if function_obj: + mapped_names.update(function_obj.__dict__) + # Add the markers to the keywords as we no longer handle them correctly. + mapped_names.update(mark.name for mark in item.iter_markers()) -def deselect_by_keyword(items, config): + return cls(mapped_names) + + def __call__(self, subname: str) -> bool: + subname = subname.lower() + names = (name.lower() for name in self._names) + + for name in names: + if subname in name: + return True + return False + + +def deselect_by_keyword(items: "List[Item]", config: Config) -> None: keywordexpr = config.option.keyword.lstrip() if not keywordexpr: return if keywordexpr.startswith("-"): + # To be removed in pytest 7.0.0. + warnings.warn(MINUS_K_DASH, stacklevel=2) keywordexpr = "not " + keywordexpr[1:] selectuntil = False if keywordexpr[-1:] == ":": + # To be removed in pytest 7.0.0. + warnings.warn(MINUS_K_COLON, stacklevel=2) selectuntil = True keywordexpr = keywordexpr[:-1] + try: + expression = Expression.compile(keywordexpr) + except ParseError as e: + raise UsageError( + "Wrong expression passed to '-k': {}: {}".format(keywordexpr, e) + ) from None + remaining = [] deselected = [] for colitem in items: - if keywordexpr and not matchkeyword(colitem, keywordexpr): + if keywordexpr and not expression.evaluate(KeywordMatcher.from_item(colitem)): deselected.append(colitem) else: if selectuntil: @@ -126,15 +219,40 @@ def deselect_by_keyword(items, config): items[:] = remaining -def deselect_by_mark(items, config): +@attr.s(slots=True) +class MarkMatcher: + """A matcher for markers which are present. + + Tries to match on any marker names, attached to the given colitem. + """ + + own_mark_names = attr.ib() + + @classmethod + def from_item(cls, item) -> "MarkMatcher": + mark_names = {mark.name for mark in item.iter_markers()} + return cls(mark_names) + + def __call__(self, name: str) -> bool: + return name in self.own_mark_names + + +def deselect_by_mark(items: "List[Item]", config: Config) -> None: matchexpr = config.option.markexpr if not matchexpr: return + try: + expression = Expression.compile(matchexpr) + except ParseError as e: + raise UsageError( + "Wrong expression passed to '-m': {}: {}".format(matchexpr, e) + ) from None + remaining = [] deselected = [] for item in items: - if matchmark(item, matchexpr): + if expression.evaluate(MarkMatcher.from_item(item)): remaining.append(item) else: deselected.append(item) @@ -144,13 +262,13 @@ def deselect_by_mark(items, config): items[:] = remaining -def pytest_collection_modifyitems(items, config): +def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None: deselect_by_keyword(items, config) deselect_by_mark(items, config) -def pytest_configure(config): - config._old_mark_config = MARK_GEN._config +def pytest_configure(config: Config) -> None: + config._store[old_mark_config_key] = MARK_GEN._config MARK_GEN._config = config empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) @@ -162,5 +280,5 @@ def pytest_configure(config): ) -def pytest_unconfigure(config): - MARK_GEN._config = getattr(config, "_old_mark_config", None) +def pytest_unconfigure(config: Config) -> None: + MARK_GEN._config = config._store.get(old_mark_config_key, None) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/evaluate.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/evaluate.py deleted file mode 100644 index 506546e253a..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/evaluate.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import platform -import sys -import traceback - -import six - -from ..outcomes import fail -from ..outcomes import TEST_OUTCOME - - -def cached_eval(config, expr, d): - if not hasattr(config, "_evalcache"): - config._evalcache = {} - try: - return config._evalcache[expr] - except KeyError: - import _pytest._code - - exprcode = _pytest._code.compile(expr, mode="eval") - config._evalcache[expr] = x = eval(exprcode, d) - return x - - -class MarkEvaluator(object): - def __init__(self, item, name): - self.item = item - self._marks = None - self._mark = None - self._mark_name = name - - def __bool__(self): - # dont cache here to prevent staleness - return bool(self._get_marks()) - - __nonzero__ = __bool__ - - def wasvalid(self): - return not hasattr(self, "exc") - - def _get_marks(self): - return list(self.item.iter_markers(name=self._mark_name)) - - def invalidraise(self, exc): - raises = self.get("raises") - if not raises: - return - return not isinstance(exc, raises) - - def istrue(self): - try: - return self._istrue() - except TEST_OUTCOME: - self.exc = sys.exc_info() - if isinstance(self.exc[1], SyntaxError): - msg = [" " * (self.exc[1].offset + 4) + "^"] - msg.append("SyntaxError: invalid syntax") - else: - msg = traceback.format_exception_only(*self.exc[:2]) - fail( - "Error evaluating %r expression\n" - " %s\n" - "%s" % (self._mark_name, self.expr, "\n".join(msg)), - pytrace=False, - ) - - def _getglobals(self): - d = {"os": os, "sys": sys, "platform": platform, "config": self.item.config} - if hasattr(self.item, "obj"): - d.update(self.item.obj.__globals__) - return d - - def _istrue(self): - if hasattr(self, "result"): - return self.result - self._marks = self._get_marks() - - if self._marks: - self.result = False - for mark in self._marks: - self._mark = mark - if "condition" in mark.kwargs: - args = (mark.kwargs["condition"],) - else: - args = mark.args - - for expr in args: - self.expr = expr - if isinstance(expr, six.string_types): - d = self._getglobals() - result = cached_eval(self.item.config, expr, d) - else: - if "reason" not in mark.kwargs: - # XXX better be checked at collection time - msg = ( - "you need to specify reason=STRING " - "when using booleans as conditions." - ) - fail(msg) - result = bool(expr) - if result: - self.result = True - self.reason = mark.kwargs.get("reason", None) - self.expr = expr - return self.result - - if not args: - self.result = True - self.reason = mark.kwargs.get("reason", None) - return self.result - return False - - def get(self, attr, default=None): - if self._mark is None: - return default - return self._mark.kwargs.get(attr, default) - - def getexplanation(self): - expl = getattr(self, "reason", None) or self.get("reason", None) - if not expl: - if not hasattr(self, "expr"): - return "" - else: - return "condition: " + str(self.expr) - return expl diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py new file mode 100644 index 00000000000..f5700109757 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/expression.py @@ -0,0 +1,224 @@ +r"""Evaluate match expressions, as used by `-k` and `-m`. + +The grammar is: + +expression: expr? EOF +expr: and_expr ('or' and_expr)* +and_expr: not_expr ('and' not_expr)* +not_expr: 'not' not_expr | '(' expr ')' | ident +ident: (\w|:|\+|-|\.|\[|\])+ + +The semantics are: + +- Empty expression evaluates to False. +- ident evaluates to True of False according to a provided matcher function. +- or/and/not evaluate according to the usual boolean semantics. +""" +import ast +import enum +import re +import types +from typing import Callable +from typing import Iterator +from typing import Mapping +from typing import Optional +from typing import Sequence + +import attr + +from _pytest.compat import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import NoReturn + + +__all__ = [ + "Expression", + "ParseError", +] + + +class TokenType(enum.Enum): + LPAREN = "left parenthesis" + RPAREN = "right parenthesis" + OR = "or" + AND = "and" + NOT = "not" + IDENT = "identifier" + EOF = "end of input" + + +@attr.s(frozen=True, slots=True) +class Token: + type = attr.ib(type=TokenType) + value = attr.ib(type=str) + pos = attr.ib(type=int) + + +class ParseError(Exception): + """The expression contains invalid syntax. + + :param column: The column in the line where the error occurred (1-based). + :param message: A description of the error. + """ + + def __init__(self, column: int, message: str) -> None: + self.column = column + self.message = message + + def __str__(self) -> str: + return "at column {}: {}".format(self.column, self.message) + + +class Scanner: + __slots__ = ("tokens", "current") + + def __init__(self, input: str) -> None: + self.tokens = self.lex(input) + self.current = next(self.tokens) + + def lex(self, input: str) -> Iterator[Token]: + pos = 0 + while pos < len(input): + if input[pos] in (" ", "\t"): + pos += 1 + elif input[pos] == "(": + yield Token(TokenType.LPAREN, "(", pos) + pos += 1 + elif input[pos] == ")": + yield Token(TokenType.RPAREN, ")", pos) + pos += 1 + else: + match = re.match(r"(:?\w|:|\+|-|\.|\[|\])+", input[pos:]) + if match: + value = match.group(0) + if value == "or": + yield Token(TokenType.OR, value, pos) + elif value == "and": + yield Token(TokenType.AND, value, pos) + elif value == "not": + yield Token(TokenType.NOT, value, pos) + else: + yield Token(TokenType.IDENT, value, pos) + pos += len(value) + else: + raise ParseError( + pos + 1, 'unexpected character "{}"'.format(input[pos]), + ) + yield Token(TokenType.EOF, "", pos) + + def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: + if self.current.type is type: + token = self.current + if token.type is not TokenType.EOF: + self.current = next(self.tokens) + return token + if reject: + self.reject((type,)) + return None + + def reject(self, expected: Sequence[TokenType]) -> "NoReturn": + raise ParseError( + self.current.pos + 1, + "expected {}; got {}".format( + " OR ".join(type.value for type in expected), self.current.type.value, + ), + ) + + +# True, False and None are legal match expression identifiers, +# but illegal as Python identifiers. To fix this, this prefix +# is added to identifiers in the conversion to Python AST. +IDENT_PREFIX = "$" + + +def expression(s: Scanner) -> ast.Expression: + if s.accept(TokenType.EOF): + ret = ast.NameConstant(False) # type: ast.expr + else: + ret = expr(s) + s.accept(TokenType.EOF, reject=True) + return ast.fix_missing_locations(ast.Expression(ret)) + + +def expr(s: Scanner) -> ast.expr: + ret = and_expr(s) + while s.accept(TokenType.OR): + rhs = and_expr(s) + ret = ast.BoolOp(ast.Or(), [ret, rhs]) + return ret + + +def and_expr(s: Scanner) -> ast.expr: + ret = not_expr(s) + while s.accept(TokenType.AND): + rhs = not_expr(s) + ret = ast.BoolOp(ast.And(), [ret, rhs]) + return ret + + +def not_expr(s: Scanner) -> ast.expr: + if s.accept(TokenType.NOT): + return ast.UnaryOp(ast.Not(), not_expr(s)) + if s.accept(TokenType.LPAREN): + ret = expr(s) + s.accept(TokenType.RPAREN, reject=True) + return ret + ident = s.accept(TokenType.IDENT) + if ident: + return ast.Name(IDENT_PREFIX + ident.value, ast.Load()) + s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT)) + + +class MatcherAdapter(Mapping[str, bool]): + """Adapts a matcher function to a locals mapping as required by eval().""" + + def __init__(self, matcher: Callable[[str], bool]) -> None: + self.matcher = matcher + + def __getitem__(self, key: str) -> bool: + return self.matcher(key[len(IDENT_PREFIX) :]) + + def __iter__(self) -> Iterator[str]: + raise NotImplementedError() + + def __len__(self) -> int: + raise NotImplementedError() + + +class Expression: + """A compiled match expression as used by -k and -m. + + The expression can be evaulated against different matchers. + """ + + __slots__ = ("code",) + + def __init__(self, code: types.CodeType) -> None: + self.code = code + + @classmethod + def compile(self, input: str) -> "Expression": + """Compile a match expression. + + :param input: The input expression - one line. + """ + astexpr = expression(Scanner(input)) + code = compile( + astexpr, filename="<pytest match expression>", mode="eval", + ) # type: types.CodeType + return Expression(code) + + def evaluate(self, matcher: Callable[[str], bool]) -> bool: + """Evaluate the match expression. + + :param matcher: + Given an identifier, should return whether it matches or not. + Should be prepared to handle arbitrary strings as input. + + :returns: Whether the expression matches or not. + """ + ret = eval( + self.code, {"__builtins__": {}}, MatcherAdapter(matcher) + ) # type: bool + return ret diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/legacy.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/legacy.py deleted file mode 100644 index c56482f14d0..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/legacy.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -""" -this is a place where we put datastructures used by legacy apis -we hope ot remove -""" -import keyword - -import attr - -from _pytest.config import UsageError - - -@attr.s -class MarkMapping(object): - """Provides a local mapping for markers where item access - resolves to True if the marker is present. """ - - own_mark_names = attr.ib() - - @classmethod - def from_item(cls, item): - mark_names = {mark.name for mark in item.iter_markers()} - return cls(mark_names) - - def __getitem__(self, name): - return name in self.own_mark_names - - -class KeywordMapping(object): - """Provides a local mapping for keywords. - Given a list of names, map any substring of one of these names to True. - """ - - def __init__(self, names): - self._names = names - - @classmethod - def from_item(cls, item): - mapped_names = set() - - # Add the names of the current item and any parent items - import pytest - - for item in item.listchain(): - if not isinstance(item, pytest.Instance): - mapped_names.add(item.name) - - # Add the names added as extra keywords to current or parent items - mapped_names.update(item.listextrakeywords()) - - # Add the names attached to the current function through direct assignment - if hasattr(item, "function"): - mapped_names.update(item.function.__dict__) - - # add the markers to the keywords as we no longer handle them correctly - mapped_names.update(mark.name for mark in item.iter_markers()) - - return cls(mapped_names) - - def __getitem__(self, subname): - for name in self._names: - if subname in name: - return True - return False - - -python_keywords_allowed_list = ["or", "and", "not"] - - -def matchmark(colitem, markexpr): - """Tries to match on any marker names, attached to the given colitem.""" - try: - return eval(markexpr, {}, MarkMapping.from_item(colitem)) - except SyntaxError as e: - raise SyntaxError(str(e) + "\nMarker expression must be valid Python!") - - -def matchkeyword(colitem, keywordexpr): - """Tries to match given keyword expression to given collector item. - - Will match on the name of colitem, including the names of its parents. - Only matches names of items which are either a :class:`Class` or a - :class:`Function`. - Additionally, matches on names in the 'extra_keyword_matches' set of - any item, as well as names directly assigned to test functions. - """ - mapping = KeywordMapping.from_item(colitem) - if " " not in keywordexpr: - # special case to allow for simple "-k pass" and "-k 1.3" - return mapping[keywordexpr] - elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]: - return not mapping[keywordexpr[4:]] - for kwd in keywordexpr.split(): - if keyword.iskeyword(kwd) and kwd not in python_keywords_allowed_list: - raise UsageError( - "Python keyword '{}' not accepted in expressions passed to '-k'".format( - kwd - ) - ) - try: - return eval(keywordexpr, {}, mapping) - except SyntaxError: - raise UsageError("Wrong expression passed to '-k': {}".format(keywordexpr)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py index 0ccd8141dd9..39a2321b3ff 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/mark/structures.py @@ -1,49 +1,68 @@ -# -*- coding: utf-8 -*- +import collections.abc import inspect +import typing import warnings -from collections import namedtuple -from operator import attrgetter +from typing import Any +from typing import Callable +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TypeVar +from typing import Union import attr -import six +from .._code import getfslineno from ..compat import ascii_escaped -from ..compat import ATTRS_EQ_FIELD -from ..compat import getfslineno -from ..compat import MappingMixin +from ..compat import final from ..compat import NOTSET -from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS +from ..compat import NotSetType +from ..compat import overload +from ..compat import TYPE_CHECKING +from _pytest.config import Config from _pytest.outcomes import fail from _pytest.warning_types import PytestUnknownMarkWarning -EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" - +if TYPE_CHECKING: + from typing import Type -def alias(name, warning=None): - getter = attrgetter(name) + from ..nodes import Node - def warned(self): - warnings.warn(warning, stacklevel=2) - return getter(self) - return property(getter if warning is None else warned, doc="alias for " + name) +EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" -def istestfunc(func): +def istestfunc(func) -> bool: return ( hasattr(func, "__call__") and getattr(func, "__name__", "<lambda>") != "<lambda>" ) -def get_empty_parameterset_mark(config, argnames, func): +def get_empty_parameterset_mark( + config: Config, argnames: Sequence[str], func +) -> "MarkDecorator": from ..nodes import Collector + fs, lineno = getfslineno(func) + reason = "got empty parameter set %r, function %s at %s:%d" % ( + argnames, + func.__name__, + fs, + lineno, + ) + requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION) if requested_mark in ("", None, "skip"): - mark = MARK_GEN.skip + mark = MARK_GEN.skip(reason=reason) elif requested_mark == "xfail": - mark = MARK_GEN.xfail(run=False) + mark = MARK_GEN.xfail(reason=reason, run=False) elif requested_mark == "fail_at_collect": f_name = func.__name__ _, lineno = getfslineno(func) @@ -52,49 +71,55 @@ def get_empty_parameterset_mark(config, argnames, func): ) else: raise LookupError(requested_mark) - fs, lineno = getfslineno(func) - reason = "got empty parameter set %r, function %s at %s:%d" % ( - argnames, - func.__name__, - fs, - lineno, - ) - return mark(reason=reason) + return mark -class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): +class ParameterSet( + NamedTuple( + "ParameterSet", + [ + ("values", Sequence[Union[object, NotSetType]]), + ("marks", "typing.Collection[Union[MarkDecorator, Mark]]"), + ("id", Optional[str]), + ], + ) +): @classmethod - def param(cls, *values, **kwargs): - marks = kwargs.pop("marks", ()) + def param( + cls, + *values: object, + marks: "Union[MarkDecorator, typing.Collection[Union[MarkDecorator, Mark]]]" = (), + id: Optional[str] = None + ) -> "ParameterSet": if isinstance(marks, MarkDecorator): marks = (marks,) else: - assert isinstance(marks, (tuple, list, set)) + # TODO(py36): Change to collections.abc.Collection. + assert isinstance(marks, (collections.abc.Sequence, set)) - id_ = kwargs.pop("id", None) - if id_ is not None: - if not isinstance(id_, six.string_types): + if id is not None: + if not isinstance(id, str): raise TypeError( - "Expected id to be a string, got {}: {!r}".format(type(id_), id_) + "Expected id to be a string, got {}: {!r}".format(type(id), id) ) - id_ = ascii_escaped(id_) - - if kwargs: - warnings.warn( - PYTEST_PARAM_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=3 - ) - return cls(values, marks, id_) + id = ascii_escaped(id) + return cls(values, marks, id) @classmethod - def extract_from(cls, parameterset, force_tuple=False): - """ + def extract_from( + cls, + parameterset: Union["ParameterSet", Sequence[object], object], + force_tuple: bool = False, + ) -> "ParameterSet": + """Extract from an object or objects. + :param parameterset: - a legacy style parameterset that may or may not be a tuple, - and may or may not be wrapped into a mess of mark objects + A legacy style parameterset that may or may not be a tuple, + and may or may not be wrapped into a mess of mark objects. :param force_tuple: - enforce tuple wrapping so single argument tuple values - don't get decomposed and break tests + Enforce tuple wrapping so single argument tuple values + don't get decomposed and break tests. """ if isinstance(parameterset, cls): @@ -102,10 +127,20 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): if force_tuple: return cls.param(parameterset) else: - return cls(parameterset, marks=[], id=None) + # TODO: Refactor to fix this type-ignore. Currently the following + # type-checks but crashes: + # + # @pytest.mark.parametrize(('x', 'y'), [1, 2]) + # def test_foo(x, y): pass + return cls(parameterset, marks=[], id=None) # type: ignore[arg-type] @staticmethod - def _parse_parametrize_args(argnames, argvalues, *args, **kwargs): + def _parse_parametrize_args( + argnames: Union[str, List[str], Tuple[str, ...]], + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + *args, + **kwargs + ) -> Tuple[Union[List[str], Tuple[str, ...]], bool]: if not isinstance(argnames, (tuple, list)): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 @@ -114,19 +149,29 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): return argnames, force_tuple @staticmethod - def _parse_parametrize_parameters(argvalues, force_tuple): + def _parse_parametrize_parameters( + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + force_tuple: bool, + ) -> List["ParameterSet"]: return [ ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues ] @classmethod - def _for_parametrize(cls, argnames, argvalues, func, config, function_definition): + def _for_parametrize( + cls, + argnames: Union[str, List[str], Tuple[str, ...]], + argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + func, + config: Config, + nodeid: str, + ) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues if parameters: - # check all parameter sets have the correct number of values + # Check all parameter sets have the correct number of values. for param in parameters: if len(param.values) != len(argnames): msg = ( @@ -137,7 +182,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): ) fail( msg.format( - nodeid=function_definition.nodeid, + nodeid=nodeid, values=param.values, names=argnames, names_len=len(argnames), @@ -146,8 +191,8 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): pytrace=False, ) else: - # empty parameter set (likely computed at runtime): create a single - # parameter set with NOTSET values, with the "empty parameter set" mark applied to it + # Empty parameter set (likely computed at runtime): create a single + # parameter set with NOTSET values, with the "empty parameter set" mark applied to it. mark = get_empty_parameterset_mark(config, argnames, func) parameters.append( ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) @@ -155,38 +200,67 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): return argnames, parameters +@final @attr.s(frozen=True) -class Mark(object): - #: name of the mark +class Mark: + #: Name of the mark. name = attr.ib(type=str) - #: positional arguments of the mark decorator - args = attr.ib() # List[object] - #: keyword arguments of the mark decorator - kwargs = attr.ib() # Dict[str, object] + #: Positional arguments of the mark decorator. + args = attr.ib(type=Tuple[Any, ...]) + #: Keyword arguments of the mark decorator. + kwargs = attr.ib(type=Mapping[str, Any]) + + #: Source Mark for ids with parametrize Marks. + _param_ids_from = attr.ib(type=Optional["Mark"], default=None, repr=False) + #: Resolved/generated ids with parametrize Marks. + _param_ids_generated = attr.ib( + type=Optional[Sequence[str]], default=None, repr=False + ) - def combined_with(self, other): - """ - :param other: the mark to combine with - :type other: Mark - :rtype: Mark + def _has_param_ids(self) -> bool: + return "ids" in self.kwargs or len(self.args) >= 4 - combines by appending args and merging the mappings + def combined_with(self, other: "Mark") -> "Mark": + """Return a new Mark which is a combination of this + Mark and another Mark. + + Combines by appending args and merging kwargs. + + :param Mark other: The mark to combine with. + :rtype: Mark """ assert self.name == other.name + + # Remember source of ids with parametrize Marks. + param_ids_from = None # type: Optional[Mark] + if self.name == "parametrize": + if other._has_param_ids(): + param_ids_from = other + elif self._has_param_ids(): + param_ids_from = self + return Mark( - self.name, self.args + other.args, dict(self.kwargs, **other.kwargs) + self.name, + self.args + other.args, + dict(self.kwargs, **other.kwargs), + param_ids_from=param_ids_from, ) +# A generic parameter designating an object to which a Mark may +# be applied -- a test function (callable) or class. +# Note: a lambda is not allowed, but this can't be represented. +_Markable = TypeVar("_Markable", bound=Union[Callable[..., object], type]) + + @attr.s -class MarkDecorator(object): - """ A decorator for test functions and test classes. When applied - it will create :class:`MarkInfo` objects which may be - :ref:`retrieved by hooks as item keywords <excontrolskip>`. - MarkDecorator instances are often created like this:: +class MarkDecorator: + """A decorator for applying a mark on test functions and classes. + + MarkDecorators are created with ``pytest.mark``:: - mark1 = pytest.mark.NAME # simple MarkDecorator - mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator + mark1 = pytest.mark.NAME # Simple MarkDecorator + mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator and can then be applied as decorators to test functions:: @@ -194,55 +268,77 @@ class MarkDecorator(object): def test_function(): pass - When a MarkDecorator instance is called it does the following: - 1. If called with a single class as its only positional argument and no - additional keyword arguments, it attaches itself to the class so it - gets applied automatically to all test cases found in that class. - 2. If called with a single function as its only positional argument and - no additional keyword arguments, it attaches a MarkInfo object to the - function, containing all the arguments already stored internally in - the MarkDecorator. - 3. When called in any other case, it performs a 'fake construction' call, - i.e. it returns a new MarkDecorator instance with the original - MarkDecorator's content updated with the arguments passed to this - call. - - Note: The rules above prevent MarkDecorator objects from storing only a - single function or class reference as their positional argument with no - additional keyword or positional arguments. + When a MarkDecorator is called it does the following: + 1. If called with a single class as its only positional argument and no + additional keyword arguments, it attaches the mark to the class so it + gets applied automatically to all test cases found in that class. + + 2. If called with a single function as its only positional argument and + no additional keyword arguments, it attaches the mark to the function, + containing all the arguments already stored internally in the + MarkDecorator. + + 3. When called in any other case, it returns a new MarkDecorator instance + with the original MarkDecorator's content updated with the arguments + passed to this call. + + Note: The rules above prevent MarkDecorators from storing only a single + function or class reference as their positional argument with no + additional keyword or positional arguments. You can work around this by + using `with_args()`. """ - mark = attr.ib(validator=attr.validators.instance_of(Mark)) + mark = attr.ib(type=Mark, validator=attr.validators.instance_of(Mark)) - name = alias("mark.name") - args = alias("mark.args") - kwargs = alias("mark.kwargs") + @property + def name(self) -> str: + """Alias for mark.name.""" + return self.mark.name @property - def markname(self): - return self.name # for backward-compat (2.4.1 had this attr) + def args(self) -> Tuple[Any, ...]: + """Alias for mark.args.""" + return self.mark.args - def __eq__(self, other): - return self.mark == other.mark if isinstance(other, MarkDecorator) else False + @property + def kwargs(self) -> Mapping[str, Any]: + """Alias for mark.kwargs.""" + return self.mark.kwargs - def __repr__(self): - return "<MarkDecorator %r>" % (self.mark,) + @property + def markname(self) -> str: + return self.name # for backward-compat (2.4.1 had this attr) - def with_args(self, *args, **kwargs): - """ return a MarkDecorator with extra arguments added + def __repr__(self) -> str: + return "<MarkDecorator {!r}>".format(self.mark) - unlike call this can be used even if the sole argument is a callable/class + def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": + """Return a MarkDecorator with extra arguments added. - :return: MarkDecorator - """ + Unlike calling the MarkDecorator, with_args() can be used even + if the sole argument is a callable/class. + :rtype: MarkDecorator + """ mark = Mark(self.name, args, kwargs) return self.__class__(self.mark.combined_with(mark)) - def __call__(self, *args, **kwargs): - """ if passed a single callable argument: decorate it with mark info. - otherwise add *args/**kwargs in-place to mark information. """ + # Type ignored because the overloads overlap with an incompatible + # return type. Not much we can do about that. Thankfully mypy picks + # the first match so it works out even if we break the rules. + @overload + def __call__(self, arg: _Markable) -> _Markable: # type: ignore[misc] + pass + + @overload # noqa: F811 + def __call__( # noqa: F811 + self, *args: object, **kwargs: object + ) -> "MarkDecorator": + pass + + def __call__(self, *args: object, **kwargs: object): # noqa: F811 + """Call the MarkDecorator.""" if args and not kwargs: func = args[0] is_class = inspect.isclass(func) @@ -252,21 +348,18 @@ class MarkDecorator(object): return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj): - """ - obtain the unpacked marks that are stored on an object - """ +def get_unpacked_marks(obj) -> List[Mark]: + """Obtain the unpacked marks that are stored on an object.""" mark_list = getattr(obj, "pytestmark", []) if not isinstance(mark_list, list): mark_list = [mark_list] return normalize_mark_list(mark_list) -def normalize_mark_list(mark_list): - """ - normalizes marker decorating helpers to mark objects +def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]: + """Normalize marker decorating helpers to mark objects. - :type mark_list: List[Union[Mark, Markdecorator]] + :type List[Union[Mark, Markdecorator]] mark_list: :rtype: List[Mark] """ extracted = [ @@ -278,32 +371,119 @@ def normalize_mark_list(mark_list): return [x for x in extracted if isinstance(x, Mark)] -def store_mark(obj, mark): - """store a Mark on an object - this is used to implement the Mark declarations/decorators correctly +def store_mark(obj, mark: Mark) -> None: + """Store a Mark on an object. + + This is used to implement the Mark declarations/decorators correctly. """ assert isinstance(mark, Mark), mark - # always reassign name to avoid updating pytestmark - # in a reference that was only borrowed + # Always reassign name to avoid updating pytestmark in a reference that + # was only borrowed. obj.pytestmark = get_unpacked_marks(obj) + [mark] -class MarkGenerator(object): - """ Factory for :class:`MarkDecorator` objects - exposed as - a ``pytest.mark`` singleton instance. Example:: +# Typing for builtin pytest marks. This is cheating; it gives builtin marks +# special privilege, and breaks modularity. But practicality beats purity... +if TYPE_CHECKING: + from _pytest.fixtures import _Scope + + class _SkipMarkDecorator(MarkDecorator): + @overload # type: ignore[override,misc] + def __call__(self, arg: _Markable) -> _Markable: + ... + + @overload # noqa: F811 + def __call__(self, reason: str = ...) -> "MarkDecorator": # noqa: F811 + ... + + class _SkipifMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + condition: Union[str, bool] = ..., + *conditions: Union[str, bool], + reason: str = ... + ) -> MarkDecorator: + ... + + class _XfailMarkDecorator(MarkDecorator): + @overload # type: ignore[override,misc] + def __call__(self, arg: _Markable) -> _Markable: + ... + + @overload # noqa: F811 + def __call__( # noqa: F811 + self, + condition: Union[str, bool] = ..., + *conditions: Union[str, bool], + reason: str = ..., + run: bool = ..., + raises: Union[ + "Type[BaseException]", Tuple["Type[BaseException]", ...] + ] = ..., + strict: bool = ... + ) -> MarkDecorator: + ... + + class _ParametrizeMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, + argnames: Union[str, List[str], Tuple[str, ...]], + argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], + *, + indirect: Union[bool, Sequence[str]] = ..., + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = ..., + scope: Optional[_Scope] = ... + ) -> MarkDecorator: + ... + + class _UsefixturesMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, *fixtures: str + ) -> MarkDecorator: + ... + + class _FilterwarningsMarkDecorator(MarkDecorator): + def __call__( # type: ignore[override] + self, *filters: str + ) -> MarkDecorator: + ... + + +@final +class MarkGenerator: + """Factory for :class:`MarkDecorator` objects - exposed as + a ``pytest.mark`` singleton instance. + + Example:: import pytest + @pytest.mark.slowtest def test_function(): pass - will set a 'slowtest' :class:`MarkInfo` object - on the ``test_function`` object. """ + applies a 'slowtest' :class:`Mark` on ``test_function``. + """ + + _config = None # type: Optional[Config] + _markers = set() # type: Set[str] - _config = None - _markers = set() + # See TYPE_CHECKING above. + if TYPE_CHECKING: + # TODO(py36): Change to builtin annotation syntax. + skip = _SkipMarkDecorator(Mark("skip", (), {})) + skipif = _SkipifMarkDecorator(Mark("skipif", (), {})) + xfail = _XfailMarkDecorator(Mark("xfail", (), {})) + parametrize = _ParametrizeMarkDecorator(Mark("parametrize", (), {})) + usefixtures = _UsefixturesMarkDecorator(Mark("usefixtures", (), {})) + filterwarnings = _FilterwarningsMarkDecorator(Mark("filterwarnings", (), {})) - def __getattr__(self, name): + def __getattr__(self, name: str) -> MarkDecorator: if name[0] == "_": raise AttributeError("Marker name must NOT start with underscore") @@ -327,13 +507,19 @@ class MarkGenerator(object): "{!r} not found in `markers` configuration option".format(name), pytrace=False, ) - else: - warnings.warn( - "Unknown pytest.mark.%s - is this a typo? You can register " - "custom marks to avoid this warning - for details, see " - "https://docs.pytest.org/en/latest/mark.html" % name, - PytestUnknownMarkWarning, - ) + + # Raise a specific error for common misspellings of "parametrize". + if name in ["parameterize", "parametrise", "parameterise"]: + __tracebackhide__ = True + fail("Unknown '{}' mark, did you mean 'parametrize'?".format(name)) + + warnings.warn( + "Unknown pytest.mark.%s - is this a typo? You can register " + "custom marks to avoid this warning - for details, see " + "https://docs.pytest.org/en/stable/mark.html" % name, + PytestUnknownMarkWarning, + 2, + ) return MarkDecorator(Mark(name, (), {})) @@ -341,13 +527,15 @@ class MarkGenerator(object): MARK_GEN = MarkGenerator() -class NodeKeywords(MappingMixin): - def __init__(self, node): +# TODO(py36): inherit from typing.MutableMapping[str, Any]. +@final +class NodeKeywords(collections.abc.MutableMapping): # type: ignore[type-arg] + def __init__(self, node: "Node") -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} - def __getitem__(self, key): + def __getitem__(self, key: str) -> Any: try: return self._markers[key] except KeyError: @@ -355,56 +543,24 @@ class NodeKeywords(MappingMixin): raise return self.parent.keywords[key] - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any) -> None: self._markers[key] = value - def __delitem__(self, key): + def __delitem__(self, key: str) -> None: raise ValueError("cannot delete key in keywords dict") - def __iter__(self): + def __iter__(self) -> Iterator[str]: seen = self._seen() return iter(seen) - def _seen(self): + def _seen(self) -> Set[str]: seen = set(self._markers) if self.parent is not None: seen.update(self.parent.keywords) return seen - def __len__(self): + def __len__(self) -> int: return len(self._seen()) - def __repr__(self): - return "<NodeKeywords for node %s>" % (self.node,) - - -# mypy cannot find this overload, remove when on attrs>=19.2 -@attr.s(hash=False, **{ATTRS_EQ_FIELD: False}) # type: ignore -class NodeMarkers(object): - """ - internal structure for storing marks belonging to a node - - ..warning:: - - unstable api - - """ - - own_markers = attr.ib(default=attr.Factory(list)) - - def update(self, add_markers): - """update the own markers - """ - self.own_markers.extend(add_markers) - - def find(self, name): - """ - find markers in own nodes or parent nodes - needs a better place - """ - for mark in self.own_markers: - if mark.name == name: - yield mark - - def __iter__(self): - return iter(self.own_markers) + def __repr__(self) -> str: + return "<NodeKeywords for node {}>".format(self.node) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py index e8671b0c702..bbd96779da5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/monkeypatch.py @@ -1,28 +1,37 @@ -# -*- coding: utf-8 -*- -""" monkeypatching and mocking functionality. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Monkeypatching and mocking functionality.""" import os import re import sys import warnings from contextlib import contextmanager - -import six +from typing import Any +from typing import Generator +from typing import List +from typing import MutableMapping +from typing import Optional +from typing import Tuple +from typing import TypeVar +from typing import Union import pytest +from _pytest.compat import final +from _pytest.compat import overload from _pytest.fixtures import fixture from _pytest.pathlib import Path RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$") +K = TypeVar("K") +V = TypeVar("V") + + @fixture -def monkeypatch(): - """The returned ``monkeypatch`` fixture provides these - helper methods to modify objects, dictionaries or os.environ:: +def monkeypatch() -> Generator["MonkeyPatch", None, None]: + """A convenient fixture for monkey-patching. + + The fixture provides these methods to modify objects, dictionaries or + os.environ:: monkeypatch.setattr(obj, name, value, raising=True) monkeypatch.delattr(obj, name, raising=True) @@ -33,18 +42,17 @@ def monkeypatch(): monkeypatch.syspath_prepend(path) monkeypatch.chdir(path) - All modifications will be undone after the requesting - test function or fixture has finished. The ``raising`` - parameter determines if a KeyError or AttributeError - will be raised if the set/deletion operation has no target. + All modifications will be undone after the requesting test function or + fixture has finished. The ``raising`` parameter determines if a KeyError + or AttributeError will be raised if the set/deletion operation has no target. """ mpatch = MonkeyPatch() yield mpatch mpatch.undo() -def resolve(name): - # simplified from zope.dottedname +def resolve(name: str) -> object: + # Simplified from zope.dottedname. parts = name.split(".") used = parts.pop(0) @@ -57,34 +65,37 @@ def resolve(name): pass else: continue - # we use explicit un-nesting of the handling block in order - # to avoid nested exceptions on python 3 + # We use explicit un-nesting of the handling block in order + # to avoid nested exceptions. try: __import__(used) except ImportError as ex: - # str is used for py2 vs py3 expected = str(ex).split()[-1] if expected == used: raise else: - raise ImportError("import error in %s: %s" % (used, ex)) + raise ImportError("import error in {}: {}".format(used, ex)) from ex found = annotated_getattr(found, part, used) return found -def annotated_getattr(obj, name, ann): +def annotated_getattr(obj: object, name: str, ann: str) -> object: try: obj = getattr(obj, name) - except AttributeError: + except AttributeError as e: raise AttributeError( - "%r object at %s has no attribute %r" % (type(obj).__name__, ann, name) - ) + "{!r} object at {} has no attribute {!r}".format( + type(obj).__name__, ann, name + ) + ) from e return obj -def derive_importpath(import_path, raising): - if not isinstance(import_path, six.string_types) or "." not in import_path: - raise TypeError("must be absolute import path string, not %r" % (import_path,)) +def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]: + if not isinstance(import_path, str) or "." not in import_path: # type: ignore[unreachable] + raise TypeError( + "must be absolute import path string, not {!r}".format(import_path) + ) module, attr = import_path.rsplit(".", 1) target = resolve(module) if raising: @@ -92,33 +103,39 @@ def derive_importpath(import_path, raising): return attr, target -class Notset(object): - def __repr__(self): +class Notset: + def __repr__(self) -> str: return "<notset>" notset = Notset() -class MonkeyPatch(object): - """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes. - """ +@final +class MonkeyPatch: + """Object returned by the ``monkeypatch`` fixture keeping a record of + setattr/item/env/syspath changes.""" - def __init__(self): - self._setattr = [] - self._setitem = [] - self._cwd = None - self._savesyspath = None + def __init__(self) -> None: + self._setattr = [] # type: List[Tuple[object, str, object]] + self._setitem = ( + [] + ) # type: List[Tuple[MutableMapping[Any, Any], object, object]] + self._cwd = None # type: Optional[str] + self._savesyspath = None # type: Optional[List[str]] @contextmanager - def context(self): - """ - Context manager that returns a new :class:`MonkeyPatch` object which - undoes any patching done inside the ``with`` block upon exit: + def context(self) -> Generator["MonkeyPatch", None, None]: + """Context manager that returns a new :class:`MonkeyPatch` object + which undoes any patching done inside the ``with`` block upon exit. + + Example: .. code-block:: python import functools + + def test_partial(monkeypatch): with monkeypatch.context() as m: m.setattr(functools, "partial", 3) @@ -133,25 +150,41 @@ class MonkeyPatch(object): finally: m.undo() - def setattr(self, target, name, value=notset, raising=True): - """ Set attribute value on target, memorizing the old value. - By default raise AttributeError if the attribute did not exist. + @overload + def setattr( + self, target: str, name: object, value: Notset = ..., raising: bool = ..., + ) -> None: + ... + + @overload # noqa: F811 + def setattr( # noqa: F811 + self, target: object, name: str, value: object, raising: bool = ..., + ) -> None: + ... + + def setattr( # noqa: F811 + self, + target: Union[str, object], + name: Union[object, str], + value: object = notset, + raising: bool = True, + ) -> None: + """Set attribute value on target, memorizing the old value. For convenience you can specify a string as ``target`` which will be interpreted as a dotted import path, with the last part - being the attribute name. Example: + being the attribute name. For example, ``monkeypatch.setattr("os.getcwd", lambda: "/")`` would set the ``getcwd`` function of the ``os`` module. - The ``raising`` value determines if the setattr should fail - if the attribute is not already present (defaults to True - which means it will raise). + Raises AttributeError if the attribute does not exist, unless + ``raising`` is set to False. """ __tracebackhide__ = True import inspect - if value is notset: - if not isinstance(target, six.string_types): + if isinstance(value, Notset): + if not isinstance(target, str): raise TypeError( "use setattr(target, name, value) or " "setattr(target, value) with target being a dotted " @@ -159,10 +192,17 @@ class MonkeyPatch(object): ) value = name name, target = derive_importpath(target, raising) + else: + if not isinstance(name, str): + raise TypeError( + "use setattr(target, name, value) with name being a string or " + "setattr(target, value) with target being a dotted " + "import string" + ) oldval = getattr(target, name, notset) if raising and oldval is notset: - raise AttributeError("%r has no attribute %r" % (target, name)) + raise AttributeError("{!r} has no attribute {!r}".format(target, name)) # avoid class descriptors like staticmethod/classmethod if inspect.isclass(target): @@ -170,22 +210,26 @@ class MonkeyPatch(object): self._setattr.append((target, name, oldval)) setattr(target, name, value) - def delattr(self, target, name=notset, raising=True): - """ Delete attribute ``name`` from ``target``, by default raise - AttributeError it the attribute did not previously exist. + def delattr( + self, + target: Union[object, str], + name: Union[str, Notset] = notset, + raising: bool = True, + ) -> None: + """Delete attribute ``name`` from ``target``. If no ``name`` is specified and ``target`` is a string it will be interpreted as a dotted import path with the last part being the attribute name. - If ``raising`` is set to False, no exception will be raised if the - attribute is missing. + Raises AttributeError it the attribute does not exist, unless + ``raising`` is set to False. """ __tracebackhide__ = True import inspect - if name is notset: - if not isinstance(target, six.string_types): + if isinstance(name, Notset): + if not isinstance(target, str): raise TypeError( "use delattr(target, name) or " "delattr(target) with target being a dotted " @@ -204,16 +248,16 @@ class MonkeyPatch(object): self._setattr.append((target, name, oldval)) delattr(target, name) - def setitem(self, dic, name, value): - """ Set dictionary entry ``name`` to value. """ + def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None: + """Set dictionary entry ``name`` to value.""" self._setitem.append((dic, name, dic.get(name, notset))) dic[name] = value - def delitem(self, dic, name, raising=True): - """ Delete ``name`` from dict. Raise KeyError if it doesn't exist. + def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None: + """Delete ``name`` from dict. - If ``raising`` is set to False, no exception will be raised if the - key is missing. + Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to + False. """ if name not in dic: if raising: @@ -222,21 +266,15 @@ class MonkeyPatch(object): self._setitem.append((dic, name, dic.get(name, notset))) del dic[name] - def _warn_if_env_name_is_not_str(self, name): - """On Python 2, warn if the given environment variable name is not a native str (#4056)""" - if six.PY2 and not isinstance(name, str): - warnings.warn( - pytest.PytestWarning( - "Environment variable name {!r} should be str".format(name) - ) - ) + def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: + """Set environment variable ``name`` to ``value``. - def setenv(self, name, value, prepend=None): - """ Set environment variable ``name`` to ``value``. If ``prepend`` - is a character, read the current environment variable value - and prepend the ``value`` adjoined with the ``prepend`` character.""" + If ``prepend`` is a character, read the current environment variable + value and prepend the ``value`` adjoined with the ``prepend`` + character. + """ if not isinstance(value, str): - warnings.warn( + warnings.warn( # type: ignore[unreachable] pytest.PytestWarning( "Value of environment variable {name} type should be str, but got " "{value!r} (type: {type}); converted to str implicitly".format( @@ -248,21 +286,19 @@ class MonkeyPatch(object): value = str(value) if prepend and name in os.environ: value = value + prepend + os.environ[name] - self._warn_if_env_name_is_not_str(name) self.setitem(os.environ, name, value) - def delenv(self, name, raising=True): - """ Delete ``name`` from the environment. Raise KeyError if it does - not exist. + def delenv(self, name: str, raising: bool = True) -> None: + """Delete ``name`` from the environment. - If ``raising`` is set to False, no exception will be raised if the - environment variable is missing. + Raises ``KeyError`` if it does not exist, unless ``raising`` is set to + False. """ - self._warn_if_env_name_is_not_str(name) - self.delitem(os.environ, name, raising=raising) + environ = os.environ # type: MutableMapping[str, str] + self.delitem(environ, name, raising=raising) - def syspath_prepend(self, path): - """ Prepend ``path`` to ``sys.path`` list of import locations. """ + def syspath_prepend(self, path) -> None: + """Prepend ``path`` to ``sys.path`` list of import locations.""" from pkg_resources import fixup_namespace_packages if self._savesyspath is None: @@ -275,17 +311,17 @@ class MonkeyPatch(object): # A call to syspathinsert() usually means that the caller wants to # import some dynamically created files, thus with python3 we # invalidate its import caches. - # This is especially important when any namespace package is in used, + # This is especially important when any namespace package is in use, # since then the mtime based FileFinder cache (that gets created in # this case already) gets not invalidated when writing the new files # quickly afterwards. - if sys.version_info >= (3, 3): - from importlib import invalidate_caches + from importlib import invalidate_caches + + invalidate_caches() - invalidate_caches() + def chdir(self, path) -> None: + """Change the current working directory to the specified path. - def chdir(self, path): - """ Change the current working directory to the specified path. Path can be a string or a py.path.local object. """ if self._cwd is None: @@ -293,15 +329,16 @@ class MonkeyPatch(object): if hasattr(path, "chdir"): path.chdir() elif isinstance(path, Path): - # modern python uses the fspath protocol here LEGACY + # Modern python uses the fspath protocol here LEGACY os.chdir(str(path)) else: os.chdir(path) - def undo(self): - """ Undo previous changes. This call consumes the - undo stack. Calling it a second time has no effect unless - you do more monkeypatching after the undo call. + def undo(self) -> None: + """Undo previous changes. + + This call consumes the undo stack. Calling it a second time has no + effect unless you do more monkeypatching after the undo call. There is generally no need to call `undo()`, since it is called automatically during tear-down. @@ -318,14 +355,14 @@ class MonkeyPatch(object): else: delattr(obj, name) self._setattr[:] = [] - for dictionary, name, value in reversed(self._setitem): + for dictionary, key, value in reversed(self._setitem): if value is notset: try: - del dictionary[name] + del dictionary[key] except KeyError: - pass # was already deleted, so we have the desired state + pass # Was already deleted, so we have the desired state. else: - dictionary[name] = value + dictionary[key] = value self._setitem[:] = [] if self._savesyspath is not None: sys.path[:] = self._savesyspath diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py index 206e9ae163e..3665d8d5ef4 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nodes.py @@ -1,25 +1,56 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import warnings +from functools import lru_cache +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from typing import TypeVar +from typing import Union import py -import six import _pytest._code -from _pytest.compat import getfslineno +from _pytest._code import getfslineno +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import TerminalRepr +from _pytest.compat import cached_property +from _pytest.compat import overload +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config import ConftestImportFailure +from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH +from _pytest.fixtures import FixtureDef +from _pytest.fixtures import FixtureLookupError +from _pytest.mark.structures import Mark +from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import NodeKeywords from _pytest.outcomes import fail +from _pytest.pathlib import absolutepath +from _pytest.pathlib import Path +from _pytest.store import Store + +if TYPE_CHECKING: + from typing import Type + + # Imported here due to circular import. + from _pytest.main import Session + from _pytest.warning_types import PytestWarning + from _pytest._code.code import _TracebackStyle + SEP = "/" tracebackcutdir = py.path.local(_pytest.__file__).dirpath() -def _splitnode(nodeid): +@lru_cache(maxsize=None) +def _splitnode(nodeid: str) -> Tuple[str, ...]: """Split a nodeid into constituent 'parts'. Node IDs are strings, and can be things like: @@ -32,21 +63,26 @@ def _splitnode(nodeid): [] ['testing', 'code'] ['testing', 'code', 'test_excinfo.py'] - ['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()'] + ['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo'] """ if nodeid == "": - # If there is no root node at all, return an empty list so the caller's logic can remain sane - return [] + # If there is no root node at all, return an empty list so the caller's + # logic can remain sane. + return () parts = nodeid.split(SEP) - # Replace single last element 'test_foo.py::Bar' with multiple elements 'test_foo.py', 'Bar' + # Replace single last element 'test_foo.py::Bar' with multiple elements + # 'test_foo.py', 'Bar'. parts[-1:] = parts[-1].split("::") - return parts + # Convert parts into a tuple to avoid possible errors with caching of a + # mutable type. + return tuple(parts) -def ischildnode(baseid, nodeid): +def ischildnode(baseid: str, nodeid: str) -> bool: """Return True if the nodeid is a child node of the baseid. - E.g. 'foo/bar::Baz' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp' + E.g. 'foo/bar::Baz' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', + but not of 'foo/blorp'. """ base_parts = _splitnode(baseid) node_parts = _splitnode(nodeid) @@ -55,71 +91,144 @@ def ischildnode(baseid, nodeid): return node_parts[: len(base_parts)] == base_parts -class Node(object): - """ base class for Collector and Item the test collection tree. - Collector subclasses have children, Items are terminal nodes.""" +_NodeType = TypeVar("_NodeType", bound="Node") + + +class NodeMeta(type): + def __call__(self, *k, **kw): + msg = ( + "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n" + "See " + "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent" + " for more details." + ).format(name=self.__name__) + fail(msg, pytrace=False) + + def _create(self, *k, **kw): + return super().__call__(*k, **kw) + + +class Node(metaclass=NodeMeta): + """Base class for Collector and Item, the components of the test + collection tree. + + Collector subclasses have children; Items are leaf nodes. + """ + + # Use __slots__ to make attribute access faster. + # Note that __dict__ is still available. + __slots__ = ( + "name", + "parent", + "config", + "session", + "fspath", + "_nodeid", + "_store", + "__dict__", + ) def __init__( - self, name, parent=None, config=None, session=None, fspath=None, nodeid=None - ): - #: a unique name within the scope of the parent node + self, + name: str, + parent: "Optional[Node]" = None, + config: Optional[Config] = None, + session: "Optional[Session]" = None, + fspath: Optional[py.path.local] = None, + nodeid: Optional[str] = None, + ) -> None: + #: A unique name within the scope of the parent node. self.name = name - #: the parent collector node. + #: The parent collector node. self.parent = parent - #: the pytest config object - self.config = config or parent.config + #: The pytest config object. + if config: + self.config = config # type: Config + else: + if not parent: + raise TypeError("config or parent must be provided") + self.config = parent.config - #: the session this node is part of - self.session = session or parent.session + #: The pytest session this node is part of. + if session: + self.session = session + else: + if not parent: + raise TypeError("session or parent must be provided") + self.session = parent.session - #: filesystem path where this node was collected from (can be None) + #: Filesystem path where this node was collected from (can be None). self.fspath = fspath or getattr(parent, "fspath", None) - #: keywords/markers collected from all scopes + #: Keywords/markers collected from all scopes. self.keywords = NodeKeywords(self) - #: the marker objects belonging to this node - self.own_markers = [] + #: The marker objects belonging to this node. + self.own_markers = [] # type: List[Mark] - #: allow adding of extra keywords to use for matching - self.extra_keyword_matches = set() + #: Allow adding of extra keywords to use for matching. + self.extra_keyword_matches = set() # type: Set[str] - # used for storing artificial fixturedefs for direct parametrization - self._name2pseudofixturedef = {} + # Used for storing artificial fixturedefs for direct parametrization. + self._name2pseudofixturedef = {} # type: Dict[str, FixtureDef[Any]] if nodeid is not None: assert "::()" not in nodeid self._nodeid = nodeid else: + if not self.parent: + raise TypeError("nodeid or parent must be provided") self._nodeid = self.parent.nodeid if self.name != "()": self._nodeid += "::" + self.name + # A place where plugins can store information on the node for their + # own use. Currently only intended for internal plugins. + self._store = Store() + + @classmethod + def from_parent(cls, parent: "Node", **kw): + """Public constructor for Nodes. + + This indirection got introduced in order to enable removing + the fragile logic from the node constructors. + + Subclasses can use ``super().from_parent(...)`` when overriding the + construction. + + :param parent: The parent node of this Node. + """ + if "config" in kw: + raise TypeError("config is not a valid argument for from_parent") + if "session" in kw: + raise TypeError("session is not a valid argument for from_parent") + return cls._create(parent=parent, **kw) + @property def ihook(self): - """ fspath sensitive hook proxy used to call pytest hooks""" + """fspath-sensitive hook proxy used to call pytest hooks.""" return self.session.gethookproxy(self.fspath) - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None)) + def __repr__(self) -> str: + return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None)) - def warn(self, warning): - """Issue a warning for this item. + def warn(self, warning: "PytestWarning") -> None: + """Issue a warning for this Node. - Warnings will be displayed after the test session, unless explicitly suppressed + Warnings will be displayed after the test session, unless explicitly suppressed. - :param Warning warning: the warning instance to issue. Must be a subclass of PytestWarning. + :param Warning warning: + The warning instance to issue. Must be a subclass of PytestWarning. - :raise ValueError: if ``warning`` instance is not a subclass of PytestWarning. + :raises ValueError: If ``warning`` instance is not a subclass of PytestWarning. Example usage: .. code-block:: python node.warn(PytestWarning("some message")) - """ from _pytest.warning_types import PytestWarning @@ -130,125 +239,141 @@ class Node(object): ) ) path, lineno = get_fslocation_from_item(self) + assert lineno is not None warnings.warn_explicit( - warning, - category=None, - filename=str(path), - lineno=lineno + 1 if lineno is not None else None, + warning, category=None, filename=str(path), lineno=lineno + 1, ) - # methods for ordering nodes + # Methods for ordering nodes. + @property - def nodeid(self): - """ a ::-separated string denoting its collection tree address. """ + def nodeid(self) -> str: + """A ::-separated string denoting its collection tree address.""" return self._nodeid - def __hash__(self): - return hash(self.nodeid) + def __hash__(self) -> int: + return hash(self._nodeid) - def setup(self): + def setup(self) -> None: pass - def teardown(self): + def teardown(self) -> None: pass - def listchain(self): - """ return list of all parent collectors up to self, - starting from root of collection tree. """ + def listchain(self) -> List["Node"]: + """Return list of all parent collectors up to self, starting from + the root of collection tree.""" chain = [] - item = self + item = self # type: Optional[Node] while item is not None: chain.append(item) item = item.parent chain.reverse() return chain - def add_marker(self, marker, append=True): - """dynamically add a marker object to the node. + def add_marker( + self, marker: Union[str, MarkDecorator], append: bool = True + ) -> None: + """Dynamically add a marker object to the node. - :type marker: ``str`` or ``pytest.mark.*`` object - :param marker: - ``append=True`` whether to append the marker, - if ``False`` insert at position ``0``. + :param append: + Whether to append the marker, or prepend it. """ - from _pytest.mark import MarkDecorator, MARK_GEN + from _pytest.mark import MARK_GEN - if isinstance(marker, six.string_types): - marker = getattr(MARK_GEN, marker) - elif not isinstance(marker, MarkDecorator): + if isinstance(marker, MarkDecorator): + marker_ = marker + elif isinstance(marker, str): + marker_ = getattr(MARK_GEN, marker) + else: raise ValueError("is not a string or pytest.mark.* Marker") - self.keywords[marker.name] = marker + self.keywords[marker_.name] = marker_ if append: - self.own_markers.append(marker.mark) + self.own_markers.append(marker_.mark) else: - self.own_markers.insert(0, marker.mark) + self.own_markers.insert(0, marker_.mark) - def iter_markers(self, name=None): - """ - :param name: if given, filter the results by the name attribute + def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: + """Iterate over all markers of the node. - iterate over all markers of the node + :param name: If given, filter the results by the name attribute. """ return (x[1] for x in self.iter_markers_with_node(name=name)) - def iter_markers_with_node(self, name=None): - """ - :param name: if given, filter the results by the name attribute + def iter_markers_with_node( + self, name: Optional[str] = None + ) -> Iterator[Tuple["Node", Mark]]: + """Iterate over all markers of the node. - iterate over all markers of the node - returns sequence of tuples (node, mark) + :param name: If given, filter the results by the name attribute. + :returns: An iterator of (node, mark) tuples. """ for node in reversed(self.listchain()): for mark in node.own_markers: if name is None or getattr(mark, "name", None) == name: yield node, mark - def get_closest_marker(self, name, default=None): - """return the first marker matching the name, from closest (for example function) to farther level (for example - module level). + @overload + def get_closest_marker(self, name: str) -> Optional[Mark]: + ... + + @overload # noqa: F811 + def get_closest_marker(self, name: str, default: Mark) -> Mark: # noqa: F811 + ... + + def get_closest_marker( # noqa: F811 + self, name: str, default: Optional[Mark] = None + ) -> Optional[Mark]: + """Return the first marker matching the name, from closest (for + example function) to farther level (for example module level). - :param default: fallback return value of no marker was found - :param name: name to filter by + :param default: Fallback return value if no marker was found. + :param name: Name to filter by. """ return next(self.iter_markers(name=name), default) - def listextrakeywords(self): - """ Return a set of all extra keywords in self and any parents.""" - extra_keywords = set() + def listextrakeywords(self) -> Set[str]: + """Return a set of all extra keywords in self and any parents.""" + extra_keywords = set() # type: Set[str] for item in self.listchain(): extra_keywords.update(item.extra_keyword_matches) return extra_keywords - def listnames(self): + def listnames(self) -> List[str]: return [x.name for x in self.listchain()] - def addfinalizer(self, fin): - """ register a function to be called when this node is finalized. + def addfinalizer(self, fin: Callable[[], object]) -> None: + """Register a function to be called when this node is finalized. This method can only be called when this node is active in a setup chain, for example during self.setup(). """ self.session._setupstate.addfinalizer(fin, self) - def getparent(self, cls): - """ get the next parent node (including ourself) - which is an instance of the given class""" - current = self + def getparent(self, cls: "Type[_NodeType]") -> Optional[_NodeType]: + """Get the next parent node (including self) which is an instance of + the given class.""" + current = self # type: Optional[Node] while current and not isinstance(current, cls): current = current.parent + assert current is None or isinstance(current, cls) return current - def _prunetraceback(self, excinfo): + def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: pass - def _repr_failure_py(self, excinfo, style=None): - if excinfo.errisinstance(fail.Exception): + def _repr_failure_py( + self, + excinfo: ExceptionInfo[BaseException], + style: "Optional[_TracebackStyle]" = None, + ) -> TerminalRepr: + if isinstance(excinfo.value, ConftestImportFailure): + excinfo = ExceptionInfo(excinfo.value.excinfo) + if isinstance(excinfo.value, fail.Exception): if not excinfo.value.pytrace: - return six.text_type(excinfo.value) - fm = self.session._fixturemanager - if excinfo.errisinstance(fm.FixtureLookupError): + style = "value" + if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() - tbfilter = True if self.config.getoption("fulltrace", False): style = "long" else: @@ -256,7 +381,6 @@ class Node(object): self._prunetraceback(excinfo) if len(excinfo.traceback) == 0: excinfo.traceback = tb - tbfilter = False # prunetraceback already does it if style == "auto": style = "long" # XXX should excinfo.getrepr record all data and toterminal() process it? @@ -271,9 +395,14 @@ class Node(object): else: truncate_locals = True + # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False. + # It is possible for a fixture/test to change the CWD while this code runs, which + # would then result in the user seeing confusing paths in the failure message. + # To fix this, if the CWD changed, always display the full absolute path. + # It will be better to just always display paths relative to invocation_dir, but + # this requires a lot of plumbing (#6428). try: - os.getcwd() - abspath = False + abspath = Path(os.getcwd()) != self.config.invocation_params.dir except OSError: abspath = True @@ -282,60 +411,80 @@ class Node(object): abspath=abspath, showlocals=self.config.getoption("showlocals", False), style=style, - tbfilter=tbfilter, + tbfilter=False, # pruned already, or in --fulltrace mode. truncate_locals=truncate_locals, ) - repr_failure = _repr_failure_py + def repr_failure( + self, + excinfo: ExceptionInfo[BaseException], + style: "Optional[_TracebackStyle]" = None, + ) -> Union[str, TerminalRepr]: + """Return a representation of a collection or test failure. + + :param excinfo: Exception information for the failure. + """ + return self._repr_failure_py(excinfo, style) -def get_fslocation_from_item(item): - """Tries to extract the actual location from an item, depending on available attributes: +def get_fslocation_from_item( + node: "Node", +) -> Tuple[Union[str, py.path.local], Optional[int]]: + """Try to extract the actual location from a node, depending on available attributes: - * "fslocation": a pair (path, lineno) - * "obj": a Python object that the item wraps. + * "location": a pair (path, lineno) + * "obj": a Python object that the node wraps. * "fspath": just a path - :rtype: a tuple of (str|LocalPath, int) with filename and line number. + :rtype: A tuple of (str|py.path.local, int) with filename and line number. """ - result = getattr(item, "location", None) - if result is not None: - return result[:2] - obj = getattr(item, "obj", None) + # See Item.location. + location = getattr( + node, "location", None + ) # type: Optional[Tuple[str, Optional[int], str]] + if location is not None: + return location[:2] + obj = getattr(node, "obj", None) if obj is not None: return getfslineno(obj) - return getattr(item, "fspath", "unknown location"), -1 + return getattr(node, "fspath", "unknown location"), -1 class Collector(Node): - """ Collector instances create children through collect() - and thus iteratively build a tree. - """ + """Collector instances create children through collect() and thus + iteratively build a tree.""" class CollectError(Exception): - """ an error during collection, contains a custom message. """ + """An error during collection, contains a custom message.""" - def collect(self): - """ returns a list of children (items and collectors) - for this collection node. - """ + def collect(self) -> Iterable[Union["Item", "Collector"]]: + """Return a list of children (items and collectors) for this + collection node.""" raise NotImplementedError("abstract") - def repr_failure(self, excinfo): - """ represent a collection failure. """ - if excinfo.errisinstance(self.CollectError): + # TODO: This omits the style= parameter which breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, excinfo: ExceptionInfo[BaseException] + ) -> Union[str, TerminalRepr]: + """Return a representation of a collection failure. + + :param excinfo: Exception information for the failure. + """ + if isinstance(excinfo.value, self.CollectError) and not self.config.getoption( + "fulltrace", False + ): exc = excinfo.value return str(exc.args[0]) # Respect explicit tbstyle option, but default to "short" - # (None._repr_failure_py defaults to "long" without "fulltrace" option). + # (_repr_failure_py uses "long" with "fulltrace" option always). tbstyle = self.config.getoption("tbstyle", "auto") if tbstyle == "auto": tbstyle = "short" return self._repr_failure_py(excinfo, style=tbstyle) - def _prunetraceback(self, excinfo): + def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: if hasattr(self, "fspath"): traceback = excinfo.traceback ntraceback = traceback.cut(path=self.fspath) @@ -351,8 +500,14 @@ def _check_initialpaths_for_relpath(session, fspath): class FSCollector(Collector): - def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None): - fspath = py.path.local(fspath) # xxx only for test_resultlog.py? + def __init__( + self, + fspath: py.path.local, + parent=None, + config: Optional[Config] = None, + session: Optional["Session"] = None, + nodeid: Optional[str] = None, + ) -> None: name = fspath.basename if parent is not None: rel = fspath.relto(parent.fspath) @@ -371,34 +526,58 @@ class FSCollector(Collector): if nodeid and os.sep != SEP: nodeid = nodeid.replace(os.sep, SEP) - super(FSCollector, self).__init__( - name, parent, config, session, nodeid=nodeid, fspath=fspath - ) + super().__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath) + + @classmethod + def from_parent(cls, parent, *, fspath, **kw): + """The public constructor.""" + return super().from_parent(parent=parent, fspath=fspath, **kw) + + def gethookproxy(self, fspath: py.path.local): + warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) + return self.session.gethookproxy(fspath) + + def isinitpath(self, path: py.path.local) -> bool: + warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) + return self.session.isinitpath(path) class File(FSCollector): - """ base class for collecting tests from a file. """ + """Base class for collecting tests from a file. + + :ref:`non-python tests`. + """ class Item(Node): - """ a basic test invocation item. Note that for a single function - there might be multiple test invocation items. + """A basic test invocation item. + + Note that for a single function there might be multiple test invocation items. """ nextitem = None - def __init__(self, name, parent=None, config=None, session=None, nodeid=None): - super(Item, self).__init__(name, parent, config, session, nodeid=nodeid) - self._report_sections = [] - - #: user properties is a list of tuples (name, value) that holds user - #: defined properties for this test. - self.user_properties = [] - - def add_report_section(self, when, key, content): - """ - Adds a new report section, similar to what's done internally to add stdout and - stderr captured output:: + def __init__( + self, + name, + parent=None, + config: Optional[Config] = None, + session: Optional["Session"] = None, + nodeid: Optional[str] = None, + ) -> None: + super().__init__(name, parent, config, session, nodeid=nodeid) + self._report_sections = [] # type: List[Tuple[str, str, str]] + + #: A list of tuples (name, value) that holds user defined properties + #: for this test. + self.user_properties = [] # type: List[Tuple[str, object]] + + def runtest(self) -> None: + raise NotImplementedError("runtest must be implemented by Item subclass") + + def add_report_section(self, when: str, key: str, content: str) -> None: + """Add a new report section, similar to what's done internally to add + stdout and stderr captured output:: item.add_report_section("call", "stdout", "report section contents") @@ -407,23 +586,19 @@ class Item(Node): :param str key: Name of the section, can be customized at will. Pytest uses ``"stdout"`` and ``"stderr"`` internally. - :param str content: The full contents as a string. """ if content: self._report_sections.append((when, key, content)) - def reportinfo(self): + def reportinfo(self) -> Tuple[Union[py.path.local, str], Optional[int], str]: return self.fspath, None, "" - @property - def location(self): - try: - return self._location - except AttributeError: - location = self.reportinfo() - fspath = self.session._node_location_to_relpath(location[0]) - location = (fspath, location[1], str(location[2])) - self._location = location - return location + @cached_property + def location(self) -> Tuple[str, Optional[int], str]: + location = self.reportinfo() + fspath = absolutepath(str(location[0])) + relfspath = self.session._node_location_to_relpath(fspath) + assert type(location[2]) is str + return (relfspath, location[1], location[2]) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nose.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nose.py index fbab91da249..bb8f99772ac 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nose.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/nose.py @@ -1,45 +1,17 @@ -# -*- coding: utf-8 -*- -""" run test suites written for nose. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import sys - -import six - -import pytest +"""Run testsuites written for nose.""" from _pytest import python -from _pytest import runner from _pytest import unittest from _pytest.config import hookimpl - - -def get_skip_exceptions(): - skip_classes = set() - for module_name in ("unittest", "unittest2", "nose"): - mod = sys.modules.get(module_name) - if hasattr(mod, "SkipTest"): - skip_classes.add(mod.SkipTest) - return tuple(skip_classes) - - -def pytest_runtest_makereport(item, call): - if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): - # let's substitute the excinfo with a pytest.skip one - call2 = runner.CallInfo.from_call( - lambda: pytest.skip(six.text_type(call.excinfo.value)), call.when - ) - call.excinfo = call2.excinfo +from _pytest.nodes import Item @hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): if not call_optional(item.obj, "setup"): - # call module level setup if there is no object level one + # Call module level setup if there is no object level one. call_optional(item.parent.obj, "setup") - # XXX this implies we only call teardown when setup worked + # XXX This implies we only call teardown when setup worked. item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item) @@ -47,14 +19,11 @@ def teardown_nose(item): if is_potential_nosetest(item): if not call_optional(item.obj, "teardown"): call_optional(item.parent.obj, "teardown") - # if hasattr(item.parent, '_nosegensetup'): - # #call_optional(item._nosegensetup, 'teardown') - # del item.parent._nosegensetup -def is_potential_nosetest(item): - # extra check needed since we do not do nose style setup/teardown - # on direct unittest style classes +def is_potential_nosetest(item: Item) -> bool: + # Extra check needed since we do not do nose style setup/teardown + # on direct unittest style classes. return isinstance(item, python.Function) and not isinstance( item, unittest.TestCaseFunction ) @@ -65,6 +34,6 @@ def call_optional(obj, name): isfixture = hasattr(method, "_pytestfixturefunction") if method is not None and not isfixture and callable(method): # If there's any problems allow the exception to raise rather than - # silently ignoring them + # silently ignoring them. method() return True diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py index 4620f957c7d..a2ddc3a1f1a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/outcomes.py @@ -1,34 +1,46 @@ -# -*- coding: utf-8 -*- -""" -exception classes and constants handling test outcomes -as well as functions creating them -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Exception classes and constants handling test outcomes as well as +functions creating them.""" import sys +from typing import Any +from typing import Callable +from typing import cast +from typing import Optional +from typing import TypeVar -from packaging.version import Version +TYPE_CHECKING = False # Avoid circular import through compat. +if TYPE_CHECKING: + from typing import NoReturn + from typing import Type # noqa: F401 (used in type string) + from typing_extensions import Protocol +else: + # typing.Protocol is only available starting from Python 3.8. It is also + # available from typing_extensions, but we don't want a runtime dependency + # on that. So use a dummy runtime implementation. + from typing import Generic + + Protocol = Generic -class OutcomeException(BaseException): - """ OutcomeException and its subclass instances indicate and - contain info about test and collection outcomes. - """ - def __init__(self, msg=None, pytrace=True): +class OutcomeException(BaseException): + """OutcomeException and its subclass instances indicate and contain info + about test and collection outcomes.""" + + def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None: + if msg is not None and not isinstance(msg, str): + error_msg = ( # type: ignore[unreachable] + "{} expected string as 'msg' parameter, got '{}' instead.\n" + "Perhaps you meant to use a mark?" + ) + raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__)) BaseException.__init__(self, msg) self.msg = msg self.pytrace = pytrace - def __repr__(self): + def __repr__(self) -> str: if self.msg: - val = self.msg - if isinstance(val, bytes): - val = val.decode("UTF-8", errors="replace") - return val - return "<%s instance>" % (self.__class__.__name__,) + return self.msg + return "<{} instance>".format(self.__class__.__name__) __str__ = __repr__ @@ -41,143 +53,171 @@ class Skipped(OutcomeException): # in order to have Skipped exception printing shorter/nicer __module__ = "builtins" - def __init__(self, msg=None, pytrace=True, allow_module_level=False): + def __init__( + self, + msg: Optional[str] = None, + pytrace: bool = True, + allow_module_level: bool = False, + ) -> None: OutcomeException.__init__(self, msg=msg, pytrace=pytrace) self.allow_module_level = allow_module_level class Failed(OutcomeException): - """ raised from an explicit call to pytest.fail() """ + """Raised from an explicit call to pytest.fail().""" __module__ = "builtins" class Exit(Exception): - """ raised for immediate program exits (no tracebacks/summaries)""" + """Raised for immediate program exits (no tracebacks/summaries).""" - def __init__(self, msg="unknown reason", returncode=None): + def __init__( + self, msg: str = "unknown reason", returncode: Optional[int] = None + ) -> None: self.msg = msg self.returncode = returncode - super(Exit, self).__init__(msg) + super().__init__(msg) -# exposed helper methods +# Elaborate hack to work around https://github.com/python/mypy/issues/2087. +# Ideally would just be `exit.Exception = Exit` etc. +_F = TypeVar("_F", bound=Callable[..., object]) +_ET = TypeVar("_ET", bound="Type[BaseException]") -def exit(msg, returncode=None): - """ - Exit testing process. - :param str msg: message to display upon exit. - :param int returncode: return code to be used when exiting pytest. - """ - __tracebackhide__ = True - raise Exit(msg, returncode) +class _WithException(Protocol[_F, _ET]): + Exception = None # type: _ET + __call__ = None # type: _F + + +def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _ET]]: + def decorate(func: _F) -> _WithException[_F, _ET]: + func_with_exception = cast(_WithException[_F, _ET], func) + func_with_exception.Exception = exception_type + return func_with_exception + + return decorate -exit.Exception = Exit +# Exposed helper methods. -def skip(msg="", **kwargs): +@_with_exception(Exit) +def exit(msg: str, returncode: Optional[int] = None) -> "NoReturn": + """Exit testing process. + + :param str msg: Message to display upon exit. + :param int returncode: Return code to be used when exiting pytest. """ - Skip an executing test with the given message. + __tracebackhide__ = True + raise Exit(msg, returncode) + + +@_with_exception(Skipped) +def skip(msg: str = "", *, allow_module_level: bool = False) -> "NoReturn": + """Skip an executing test with the given message. This function should be called only during testing (setup, call or teardown) or during collection by using the ``allow_module_level`` flag. This function can be called in doctests as well. - :kwarg bool allow_module_level: allows this function to be called at - module level, skipping the rest of the module. Default to False. + :param bool allow_module_level: + Allows this function to be called at module level, skipping the rest + of the module. Defaults to False. .. note:: - It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be - skipped under certain conditions like mismatching platforms or - dependencies. + It is better to use the :ref:`pytest.mark.skipif ref` marker when + possible to declare a test to be skipped under certain conditions + like mismatching platforms or dependencies. Similarly, use the ``# doctest: +SKIP`` directive (see `doctest.SKIP <https://docs.python.org/3/library/doctest.html#doctest.SKIP>`_) to skip a doctest statically. """ __tracebackhide__ = True - allow_module_level = kwargs.pop("allow_module_level", False) - if kwargs: - raise TypeError("unexpected keyword arguments: {}".format(sorted(kwargs))) raise Skipped(msg=msg, allow_module_level=allow_module_level) -skip.Exception = Skipped - +@_with_exception(Failed) +def fail(msg: str = "", pytrace: bool = True) -> "NoReturn": + """Explicitly fail an executing test with the given message. -def fail(msg="", pytrace=True): - """ - Explicitly fail an executing test with the given message. - - :param str msg: the message to show the user as reason for the failure. - :param bool pytrace: if false the msg represents the full failure information and no + :param str msg: + The message to show the user as reason for the failure. + :param bool pytrace: + If False, msg represents the full failure information and no python traceback will be reported. """ __tracebackhide__ = True raise Failed(msg=msg, pytrace=pytrace) -fail.Exception = Failed - - -class XFailed(fail.Exception): - """ raised from an explicit call to pytest.xfail() """ +class XFailed(Failed): + """Raised from an explicit call to pytest.xfail().""" -def xfail(reason=""): - """ - Imperatively xfail an executing test or setup functions with the given reason. +@_with_exception(XFailed) +def xfail(reason: str = "") -> "NoReturn": + """Imperatively xfail an executing test or setup function with the given reason. This function should be called only during testing (setup, call or teardown). .. note:: - It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be - xfailed under certain conditions like known bugs or missing features. + It is better to use the :ref:`pytest.mark.xfail ref` marker when + possible to declare a test to be xfailed under certain conditions + like known bugs or missing features. """ __tracebackhide__ = True raise XFailed(reason) -xfail.Exception = XFailed +def importorskip( + modname: str, minversion: Optional[str] = None, reason: Optional[str] = None +) -> Any: + """Import and return the requested module ``modname``, or skip the + current test if the module cannot be imported. + + :param str modname: + The name of the module to import. + :param str minversion: + If given, the imported module's ``__version__`` attribute must be at + least this minimal version, otherwise the test is still skipped. + :param str reason: + If given, this reason is shown as the message when the module cannot + be imported. + :returns: + The imported module. This should be assigned to its canonical name. -def importorskip(modname, minversion=None, reason=None): - """Imports and returns the requested module ``modname``, or skip the current test - if the module cannot be imported. + Example:: - :param str modname: the name of the module to import - :param str minversion: if given, the imported module ``__version__`` attribute must be - at least this minimal version, otherwise the test is still skipped. - :param str reason: if given, this reason is shown as the message when the module - cannot be imported. + docutils = pytest.importorskip("docutils") """ import warnings __tracebackhide__ = True compile(modname, "", "eval") # to catch syntaxerrors - import_exc = None with warnings.catch_warnings(): - # make sure to ignore ImportWarnings that might happen because + # Make sure to ignore ImportWarnings that might happen because # of existing directories with the same name we're trying to - # import but without a __init__.py file + # import but without a __init__.py file. warnings.simplefilter("ignore") try: __import__(modname) except ImportError as exc: - # Do not raise chained exception here(#1485) - import_exc = exc - if import_exc: - if reason is None: - reason = "could not import %r: %s" % (modname, import_exc) - raise Skipped(reason, allow_module_level=True) + if reason is None: + reason = "could not import {!r}: {}".format(modname, exc) + raise Skipped(reason, allow_module_level=True) from None mod = sys.modules[modname] if minversion is None: return mod verattr = getattr(mod, "__version__", None) if minversion is not None: + # Imported lazily to improve start-up time. + from packaging.version import Version + if verattr is None or Version(verattr) < Version(minversion): raise Skipped( "module %r has __version__ %r, required is: %r" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py index 7a3e80231c2..0546d237762 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pastebin.py @@ -1,18 +1,21 @@ -# -*- coding: utf-8 -*- -""" submit failure or test session information to a pastebin service. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import sys +"""Submit failure or test session information to a pastebin service.""" import tempfile - -import six +from io import StringIO +from typing import IO +from typing import Union import pytest +from _pytest.config import Config +from _pytest.config import create_terminal_writer +from _pytest.config.argparsing import Parser +from _pytest.store import StoreKey +from _pytest.terminal import TerminalReporter + + +pastebinfile_key = StoreKey[IO[bytes]]() -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting") group._addoption( "--pastebin", @@ -26,85 +29,82 @@ def pytest_addoption(parser): @pytest.hookimpl(trylast=True) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: if config.option.pastebin == "all": tr = config.pluginmanager.getplugin("terminalreporter") - # if no terminal reporter plugin is present, nothing we can do here; - # this can happen when this function executes in a slave node - # when using pytest-xdist, for example + # If no terminal reporter plugin is present, nothing we can do here; + # this can happen when this function executes in a worker node + # when using pytest-xdist, for example. if tr is not None: - # pastebin file will be utf-8 encoded binary file - config._pastebinfile = tempfile.TemporaryFile("w+b") + # pastebin file will be UTF-8 encoded binary file. + config._store[pastebinfile_key] = tempfile.TemporaryFile("w+b") oldwrite = tr._tw.write def tee_write(s, **kwargs): oldwrite(s, **kwargs) - if isinstance(s, six.text_type): + if isinstance(s, str): s = s.encode("utf-8") - config._pastebinfile.write(s) + config._store[pastebinfile_key].write(s) tr._tw.write = tee_write -def pytest_unconfigure(config): - if hasattr(config, "_pastebinfile"): - # get terminal contents and delete file - config._pastebinfile.seek(0) - sessionlog = config._pastebinfile.read() - config._pastebinfile.close() - del config._pastebinfile - # undo our patching in the terminal reporter +def pytest_unconfigure(config: Config) -> None: + if pastebinfile_key in config._store: + pastebinfile = config._store[pastebinfile_key] + # Get terminal contents and delete file. + pastebinfile.seek(0) + sessionlog = pastebinfile.read() + pastebinfile.close() + del config._store[pastebinfile_key] + # Undo our patching in the terminal reporter. tr = config.pluginmanager.getplugin("terminalreporter") del tr._tw.__dict__["write"] - # write summary + # Write summary. tr.write_sep("=", "Sending information to Paste Service") pastebinurl = create_new_paste(sessionlog) tr.write_line("pastebin session-log: %s\n" % pastebinurl) -def create_new_paste(contents): - """ - Creates a new paste using bpaste.net service. +def create_new_paste(contents: Union[str, bytes]) -> str: + """Create a new paste using the bpaste.net service. - :contents: paste contents as utf-8 encoded bytes - :returns: url to the pasted contents + :contents: Paste contents string. + :returns: URL to the pasted contents, or an error message. """ import re - - if sys.version_info < (3, 0): - from urllib import urlopen, urlencode - else: - from urllib.request import urlopen - from urllib.parse import urlencode + from urllib.request import urlopen + from urllib.parse import urlencode params = {"code": contents, "lexer": "text", "expiry": "1week"} url = "https://bpaste.net" - response = urlopen(url, data=urlencode(params).encode("ascii")).read() - m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8")) + try: + response = ( + urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") + ) # type: str + except OSError as exc_info: # urllib errors + return "bad response: %s" % exc_info + m = re.search(r'href="/raw/(\w+)"', response) if m: - return "%s/show/%s" % (url, m.group(1)) + return "{}/show/{}".format(url, m.group(1)) else: - return "bad response: " + response - + return "bad response: invalid format ('" + response + "')" -def pytest_terminal_summary(terminalreporter): - import _pytest.config +def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: if terminalreporter.config.option.pastebin != "failed": return - tr = terminalreporter - if "failed" in tr.stats: + if "failed" in terminalreporter.stats: terminalreporter.write_sep("=", "Sending information to Paste Service") - for rep in terminalreporter.stats.get("failed"): + for rep in terminalreporter.stats["failed"]: try: msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc except AttributeError: - msg = tr._getfailureheadline(rep) - tw = _pytest.config.create_terminal_writer( - terminalreporter.config, stringio=True - ) + msg = terminalreporter._getfailureheadline(rep) + file = StringIO() + tw = create_terminal_writer(terminalreporter.config, file) rep.toterminal(tw) - s = tw.stringio.getvalue() + s = file.getvalue() assert len(s) pastebinurl = create_new_paste(s) - tr.write_line("%s --> %s" % (msg, pastebinurl)) + terminalreporter.write_line("{} --> {}".format(msg, pastebinurl)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py index 42071f43104..355281039fd 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pathlib.py @@ -1,31 +1,36 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import atexit -import errno +import contextlib import fnmatch +import importlib.util import itertools -import operator import os import shutil import sys import uuid import warnings +from enum import Enum from functools import partial -from functools import reduce from os.path import expanduser from os.path import expandvars from os.path import isabs from os.path import sep from posixpath import sep as posix_sep - -import six -from six.moves import map - -from .compat import PY36 +from types import ModuleType +from typing import Callable +from typing import Iterable +from typing import Iterator +from typing import Optional +from typing import Set +from typing import TypeVar +from typing import Union + +import py + +from _pytest.compat import assert_never +from _pytest.outcomes import skip from _pytest.warning_types import PytestWarning -if PY36: +if sys.version_info[:2] >= (3, 6): from pathlib import Path, PurePath else: from pathlib2 import Path, PurePath @@ -35,35 +40,34 @@ __all__ = ["Path", "PurePath"] LOCK_TIMEOUT = 60 * 60 * 3 -get_lock_path = operator.methodcaller("joinpath", ".lock") +_AnyPurePath = TypeVar("_AnyPurePath", bound=PurePath) -def ensure_reset_dir(path): - """ - ensures the given path is an empty directory - """ + +def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: + return path.joinpath(".lock") + + +def ensure_reset_dir(path: Path) -> None: + """Ensure the given path is an empty directory.""" if path.exists(): rm_rf(path) path.mkdir() -def on_rm_rf_error(func, path, exc, **kwargs): - """Handles known read-only errors during rmtree. +def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool: + """Handle known read-only errors during rmtree. The returned value is used only by our own tests. """ - start_path = kwargs["start_path"] exctype, excvalue = exc[:2] - # another process removed the file in the middle of the "rm_rf" (xdist for example) - # more context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018 - if isinstance(excvalue, OSError) and excvalue.errno == errno.ENOENT: + # Another process removed the file in the middle of the "rm_rf" (xdist for example). + # More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018 + if isinstance(excvalue, FileNotFoundError): return False - if not isinstance(excvalue, OSError) or excvalue.errno not in ( - errno.EACCES, - errno.EPERM, - ): + if not isinstance(excvalue, PermissionError): warnings.warn( PytestWarning( "(rm_rf) error removing {}\n{}: {}".format(path, exctype, excvalue) @@ -72,19 +76,20 @@ def on_rm_rf_error(func, path, exc, **kwargs): return False if func not in (os.rmdir, os.remove, os.unlink): - warnings.warn( - PytestWarning( - "(rm_rf) unknown function {} when removing {}:\n{}: {}".format( - path, func, exctype, excvalue + if func not in (os.open,): + warnings.warn( + PytestWarning( + "(rm_rf) unknown function {} when removing {}:\n{}: {}".format( + func, path, exctype, excvalue + ) ) ) - ) return False # Chmod + retry. import stat - def chmod_rw(p): + def chmod_rw(p: str) -> None: mode = os.stat(p).st_mode os.chmod(p, mode | stat.S_IRUSR | stat.S_IWUSR) @@ -94,7 +99,7 @@ def on_rm_rf_error(func, path, exc, **kwargs): if p.is_file(): for parent in p.parents: chmod_rw(str(parent)) - # stop when we reach the original path passed to rm_rf + # Stop when we reach the original path passed to rm_rf. if parent == start_path: break chmod_rw(str(path)) @@ -103,66 +108,86 @@ def on_rm_rf_error(func, path, exc, **kwargs): return True -def rm_rf(path): - """Remove the path contents recursively, even if some elements - are read-only. +def ensure_extended_length_path(path: Path) -> Path: + """Get the extended-length version of a path (Windows). + + On Windows, by default, the maximum length of a path (MAX_PATH) is 260 + characters, and operations on paths longer than that fail. But it is possible + to overcome this by converting the path to "extended-length" form before + performing the operation: + https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation + + On Windows, this function returns the extended-length absolute version of path. + On other platforms it returns path unchanged. """ + if sys.platform.startswith("win32"): + path = path.resolve() + path = Path(get_extended_length_path_str(str(path))) + return path + + +def get_extended_length_path_str(path: str) -> str: + """Convert a path to a Windows extended length path.""" + long_path_prefix = "\\\\?\\" + unc_long_path_prefix = "\\\\?\\UNC\\" + if path.startswith((long_path_prefix, unc_long_path_prefix)): + return path + # UNC + if path.startswith("\\\\"): + return unc_long_path_prefix + path[2:] + return long_path_prefix + path + + +def rm_rf(path: Path) -> None: + """Remove the path contents recursively, even if some elements + are read-only.""" + path = ensure_extended_length_path(path) onerror = partial(on_rm_rf_error, start_path=path) shutil.rmtree(str(path), onerror=onerror) -def find_prefixed(root, prefix): - """finds all elements in root that begin with the prefix, case insensitive""" +def find_prefixed(root: Path, prefix: str) -> Iterator[Path]: + """Find all elements in root that begin with the prefix, case insensitive.""" l_prefix = prefix.lower() for x in root.iterdir(): if x.name.lower().startswith(l_prefix): yield x -def extract_suffixes(iter, prefix): - """ - :param iter: iterator over path names - :param prefix: expected prefix of the path names - :returns: the parts of the paths following the prefix +def extract_suffixes(iter: Iterable[PurePath], prefix: str) -> Iterator[str]: + """Return the parts of the paths following the prefix. + + :param iter: Iterator over path names. + :param prefix: Expected prefix of the path names. """ p_len = len(prefix) for p in iter: yield p.name[p_len:] -def find_suffixes(root, prefix): - """combines find_prefixes and extract_suffixes - """ +def find_suffixes(root: Path, prefix: str) -> Iterator[str]: + """Combine find_prefixes and extract_suffixes.""" return extract_suffixes(find_prefixed(root, prefix), prefix) -def parse_num(maybe_num): - """parses number path suffixes, returns -1 on error""" +def parse_num(maybe_num) -> int: + """Parse number path suffixes, returns -1 on error.""" try: return int(maybe_num) except ValueError: return -1 -if six.PY2: +def _force_symlink( + root: Path, target: Union[str, PurePath], link_to: Union[str, Path] +) -> None: + """Helper to create the current symlink. - def _max(iterable, default): - """needed due to python2.7 lacking the default argument for max""" - return reduce(max, iterable, default) - - -else: - _max = max + It's full of race conditions that are reasonably OK to ignore + for the context of best effort linking to the latest test run. - -def _force_symlink(root, target, link_to): - """helper to create the current symlink - - it's full of race conditions that are reasonably ok to ignore - for the context of best effort linking to the latest testrun - - the presumption being thatin case of much parallelism - the inaccuracy is going to be acceptable + The presumption being that in case of much parallelism + the inaccuracy is going to be acceptable. """ current_symlink = root.joinpath(target) try: @@ -175,11 +200,11 @@ def _force_symlink(root, target, link_to): pass -def make_numbered_dir(root, prefix): - """create a directory with an increased number as suffix for the given prefix""" +def make_numbered_dir(root: Path, prefix: str) -> Path: + """Create a directory with an increased number as suffix for the given prefix.""" for i in range(10): # try up to 10 times to create the folder - max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1) + max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) new_number = max_existing + 1 new_path = root.joinpath("{}{}".format(prefix, new_number)) try: @@ -190,55 +215,50 @@ def make_numbered_dir(root, prefix): _force_symlink(root, prefix + "current", new_path) return new_path else: - raise EnvironmentError( + raise OSError( "could not create numbered dir with prefix " "{prefix} in {root} after 10 tries".format(prefix=prefix, root=root) ) -def create_cleanup_lock(p): - """crates a lock to prevent premature folder cleanup""" +def create_cleanup_lock(p: Path) -> Path: + """Create a lock to prevent premature folder cleanup.""" lock_path = get_lock_path(p) try: fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644) - except OSError as e: - if e.errno == errno.EEXIST: - six.raise_from( - EnvironmentError("cannot create lockfile in {path}".format(path=p)), e - ) - else: - raise + except FileExistsError as e: + raise OSError("cannot create lockfile in {path}".format(path=p)) from e else: pid = os.getpid() - spid = str(pid) - if not isinstance(spid, bytes): - spid = spid.encode("ascii") + spid = str(pid).encode() os.write(fd, spid) os.close(fd) if not lock_path.is_file(): - raise EnvironmentError("lock path got renamed after successful creation") + raise OSError("lock path got renamed after successful creation") return lock_path -def register_cleanup_lock_removal(lock_path, register=atexit.register): - """registers a cleanup function for removing a lock, by default on atexit""" +def register_cleanup_lock_removal(lock_path: Path, register=atexit.register): + """Register a cleanup function for removing a lock, by default on atexit.""" pid = os.getpid() - def cleanup_on_exit(lock_path=lock_path, original_pid=pid): + def cleanup_on_exit(lock_path: Path = lock_path, original_pid: int = pid) -> None: current_pid = os.getpid() if current_pid != original_pid: # fork return try: lock_path.unlink() - except (OSError, IOError): + except OSError: pass return register(cleanup_on_exit) -def maybe_delete_a_numbered_dir(path): - """removes a numbered directory if its lock can be obtained and it does not seem to be in use""" +def maybe_delete_a_numbered_dir(path: Path) -> None: + """Remove a numbered directory if its lock can be obtained and it does + not seem to be in use.""" + path = ensure_extended_length_path(path) lock_path = None try: lock_path = create_cleanup_lock(path) @@ -247,50 +267,59 @@ def maybe_delete_a_numbered_dir(path): garbage = parent.joinpath("garbage-{}".format(uuid.uuid4())) path.rename(garbage) rm_rf(garbage) - except (OSError, EnvironmentError): + except OSError: # known races: # * other process did a cleanup at the same time # * deletable folder was found # * process cwd (Windows) return finally: - # if we created the lock, ensure we remove it even if we failed - # to properly remove the numbered dir + # If we created the lock, ensure we remove it even if we failed + # to properly remove the numbered dir. if lock_path is not None: try: lock_path.unlink() - except (OSError, IOError): + except OSError: pass -def ensure_deletable(path, consider_lock_dead_if_created_before): - """checks if a lock exists and breaks it if its considered dead""" +def ensure_deletable(path: Path, consider_lock_dead_if_created_before: float) -> bool: + """Check if `path` is deletable based on whether the lock file is expired.""" if path.is_symlink(): return False lock = get_lock_path(path) - if not lock.exists(): - return True + try: + if not lock.is_file(): + return True + except OSError: + # we might not have access to the lock file at all, in this case assume + # we don't have access to the entire directory (#7491). + return False try: lock_time = lock.stat().st_mtime except Exception: return False else: if lock_time < consider_lock_dead_if_created_before: - lock.unlink() - return True - else: - return False + # We want to ignore any errors while trying to remove the lock such as: + # - PermissionDenied, like the file permissions have changed since the lock creation; + # - FileNotFoundError, in case another pytest process got here first; + # and any other cause of failure. + with contextlib.suppress(OSError): + lock.unlink() + return True + return False -def try_cleanup(path, consider_lock_dead_if_created_before): - """tries to cleanup a folder if we can ensure it's deletable""" +def try_cleanup(path: Path, consider_lock_dead_if_created_before: float) -> None: + """Try to cleanup a folder if we can ensure it's deletable.""" if ensure_deletable(path, consider_lock_dead_if_created_before): maybe_delete_a_numbered_dir(path) -def cleanup_candidates(root, prefix, keep): - """lists candidates for numbered directories to be removed - follows py.path""" - max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1) +def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]: + """List candidates for numbered directories to be removed - follows py.path.""" + max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1) max_delete = max_existing - keep paths = find_prefixed(root, prefix) paths, paths2 = itertools.tee(paths) @@ -300,16 +329,20 @@ def cleanup_candidates(root, prefix, keep): yield path -def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before): - """cleanup for lock driven numbered directories""" +def cleanup_numbered_dir( + root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float +) -> None: + """Cleanup for lock driven numbered directories.""" for path in cleanup_candidates(root, prefix, keep): try_cleanup(path, consider_lock_dead_if_created_before) for path in root.glob("garbage-*"): try_cleanup(path, consider_lock_dead_if_created_before) -def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout): - """creates a numbered dir with a cleanup lock and removes old ones""" +def make_numbered_dir_with_cleanup( + root: Path, prefix: str, keep: int, lock_timeout: float +) -> Path: + """Create a numbered dir with a cleanup lock and remove old ones.""" e = None for i in range(10): try: @@ -320,40 +353,41 @@ def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout): e = exc else: consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout - cleanup_numbered_dir( - root=root, - prefix=prefix, - keep=keep, - consider_lock_dead_if_created_before=consider_lock_dead_if_created_before, + # Register a cleanup for program exit + atexit.register( + cleanup_numbered_dir, + root, + prefix, + keep, + consider_lock_dead_if_created_before, ) return p assert e is not None raise e -def resolve_from_str(input, root): - assert not isinstance(input, Path), "would break on py2" - root = Path(root) +def resolve_from_str(input: str, rootpath: Path) -> Path: input = expanduser(input) input = expandvars(input) if isabs(input): return Path(input) else: - return root.joinpath(input) + return rootpath.joinpath(input) -def fnmatch_ex(pattern, path): - """FNMatcher port from py.path.common which works with PurePath() instances. +def fnmatch_ex(pattern: str, path) -> bool: + """A port of FNMatcher from py.path.common which works with PurePath() instances. - The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions - for each part of the path, while this algorithm uses the whole path instead. + The difference between this algorithm and PurePath.match() is that the + latter matches "**" glob expressions for each part of the path, while + this algorithm uses the whole path instead. For example: - "tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with - PurePath.match(). + "tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" + with this algorithm, but not with PurePath.match(). - This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according - this logic. + This algorithm was ported to keep backward-compatibility with existing + settings which assume paths match according this logic. References: * https://bugs.python.org/issue29249 @@ -371,10 +405,207 @@ def fnmatch_ex(pattern, path): if sep not in pattern: name = path.name else: - name = six.text_type(path) + name = str(path) + if path.is_absolute() and not os.path.isabs(pattern): + pattern = "*{}{}".format(os.sep, pattern) return fnmatch.fnmatch(name, pattern) -def parts(s): +def parts(s: str) -> Set[str]: parts = s.split(sep) return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} + + +def symlink_or_skip(src, dst, **kwargs): + """Make a symlink, or skip the test in case symlinks are not supported.""" + try: + os.symlink(str(src), str(dst), **kwargs) + except OSError as e: + skip("symlinks not supported: {}".format(e)) + + +class ImportMode(Enum): + """Possible values for `mode` parameter of `import_path`.""" + + prepend = "prepend" + append = "append" + importlib = "importlib" + + +class ImportPathMismatchError(ImportError): + """Raised on import_path() if there is a mismatch of __file__'s. + + This can happen when `import_path` is called multiple times with different filenames that has + the same basename but reside in packages + (for example "/tests1/test_foo.py" and "/tests2/test_foo.py"). + """ + + +def import_path( + p: Union[str, py.path.local, Path], + *, + mode: Union[str, ImportMode] = ImportMode.prepend +) -> ModuleType: + """Import and return a module from the given path, which can be a file (a module) or + a directory (a package). + + The import mechanism used is controlled by the `mode` parameter: + + * `mode == ImportMode.prepend`: the directory containing the module (or package, taking + `__init__.py` files into account) will be put at the *start* of `sys.path` before + being imported with `__import__. + + * `mode == ImportMode.append`: same as `prepend`, but the directory will be appended + to the end of `sys.path`, if not already in `sys.path`. + + * `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib` + to import the module, which avoids having to use `__import__` and muck with `sys.path` + at all. It effectively allows having same-named test modules in different places. + + :raises ImportPathMismatchError: + If after importing the given `path` and the module `__file__` + are different. Only raised in `prepend` and `append` modes. + """ + mode = ImportMode(mode) + + path = Path(str(p)) + + if not path.exists(): + raise ImportError(path) + + if mode is ImportMode.importlib: + module_name = path.stem + + for meta_importer in sys.meta_path: + spec = meta_importer.find_spec(module_name, [str(path.parent)]) + if spec is not None: + break + else: + spec = importlib.util.spec_from_file_location(module_name, str(path)) + + if spec is None: + raise ImportError( + "Can't find module {} at location {}".format(module_name, str(path)) + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) # type: ignore[union-attr] + return mod + + pkg_path = resolve_package_path(path) + if pkg_path is not None: + pkg_root = pkg_path.parent + names = list(path.with_suffix("").relative_to(pkg_root).parts) + if names[-1] == "__init__": + names.pop() + module_name = ".".join(names) + else: + pkg_root = path.parent + module_name = path.stem + + # Change sys.path permanently: restoring it at the end of this function would cause surprising + # problems because of delayed imports: for example, a conftest.py file imported by this function + # might have local imports, which would fail at runtime if we restored sys.path. + if mode is ImportMode.append: + if str(pkg_root) not in sys.path: + sys.path.append(str(pkg_root)) + elif mode is ImportMode.prepend: + if str(pkg_root) != sys.path[0]: + sys.path.insert(0, str(pkg_root)) + else: + assert_never(mode) + + importlib.import_module(module_name) + + mod = sys.modules[module_name] + if path.name == "__init__.py": + return mod + + ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "") + if ignore != "1": + module_file = mod.__file__ + if module_file.endswith((".pyc", ".pyo")): + module_file = module_file[:-1] + if module_file.endswith(os.path.sep + "__init__.py"): + module_file = module_file[: -(len(os.path.sep + "__init__.py"))] + + try: + is_same = os.path.samefile(str(path), module_file) + except FileNotFoundError: + is_same = False + + if not is_same: + raise ImportPathMismatchError(module_name, module_file, path) + + return mod + + +def resolve_package_path(path: Path) -> Optional[Path]: + """Return the Python package path by looking for the last + directory upwards which still contains an __init__.py. + + Returns None if it can not be determined. + """ + result = None + for parent in itertools.chain((path,), path.parents): + if parent.is_dir(): + if not parent.joinpath("__init__.py").is_file(): + break + if not parent.name.isidentifier(): + break + result = parent + return result + + +def visit( + path: str, recurse: Callable[["os.DirEntry[str]"], bool] +) -> Iterator["os.DirEntry[str]"]: + """Walk a directory recursively, in breadth-first order. + + Entries at each directory level are sorted. + """ + entries = sorted(os.scandir(path), key=lambda entry: entry.name) + yield from entries + for entry in entries: + if entry.is_dir(follow_symlinks=False) and recurse(entry): + yield from visit(entry.path, recurse) + + +def absolutepath(path: Union[Path, str]) -> Path: + """Convert a path to an absolute path using os.path.abspath. + + Prefer this over Path.resolve() (see #6523). + Prefer this over Path.absolute() (not public, doesn't normalize). + """ + return Path(os.path.abspath(str(path))) + + +def commonpath(path1: Path, path2: Path) -> Optional[Path]: + """Return the common part shared with the other path, or None if there is + no common part.""" + try: + return Path(os.path.commonpath((str(path1), str(path2)))) + except ValueError: + return None + + +def bestrelpath(directory: Path, dest: Path) -> str: + """Return a string which is a relative path from directory to dest such + that directory/bestrelpath == dest. + + If no such path can be determined, returns dest. + """ + if dest == directory: + return os.curdir + # Find the longest common directory. + base = commonpath(directory, dest) + # Can be the case on Windows. + if not base: + return str(dest) + reldirectory = directory.relative_to(base) + reldest = dest.relative_to(base) + return os.path.join( + # Back from directory to base. + *([os.pardir] * len(reldirectory.parts)), + # Forward from base to dest. + *reldest.parts, + ) diff --git a/tests/wpt/web-platform-tests/common/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/py.typed index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/common/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/py.typed diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py index f1d739c9917..d78062a86ce 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/pytester.py @@ -1,44 +1,66 @@ -# -*- coding: utf-8 -*- -"""(disabled by default) support for testing pytest and pytest plugins.""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import codecs +"""(Disabled by default) support for testing pytest and pytest plugins.""" +import collections.abc import gc +import importlib import os import platform import re import subprocess import sys -import time import traceback from fnmatch import fnmatch +from io import StringIO +from typing import Callable +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Union from weakref import WeakKeyDictionary import py -import six +from iniconfig import IniConfig import pytest +from _pytest import timing from _pytest._code import Source -from _pytest._io.saferepr import saferepr -from _pytest.assertion.rewrite import AssertionRewritingHook -from _pytest.capture import MultiCapture -from _pytest.capture import SysCapture -from _pytest.compat import safe_str -from _pytest.compat import Sequence -from _pytest.main import EXIT_INTERRUPTED -from _pytest.main import EXIT_OK +from _pytest.capture import _get_multicapture +from _pytest.compat import final +from _pytest.compat import overload +from _pytest.compat import TYPE_CHECKING +from _pytest.config import _PluggyPlugin +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config import PytestPluginManager +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureRequest from _pytest.main import Session from _pytest.monkeypatch import MonkeyPatch +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.pathlib import make_numbered_dir from _pytest.pathlib import Path +from _pytest.python import Module +from _pytest.reports import CollectReport +from _pytest.reports import TestReport +from _pytest.tmpdir import TempdirFactory + +if TYPE_CHECKING: + from typing import Type + from typing_extensions import Literal + + import pexpect + IGNORE_PAM = [ # filenames added when obtaining details about the current user - u"/var/lib/sss/mc/passwd" + "/var/lib/sss/mc/passwd" ] -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addoption( "--lsof", action="store_true", @@ -63,7 +85,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: if config.getvalue("lsof"): checker = LsofFdLeakChecker() if checker.matching_platform(): @@ -76,30 +98,17 @@ def pytest_configure(config): ) -def raise_on_kwargs(kwargs): - __tracebackhide__ = True - if kwargs: # pragma: no branch - raise TypeError( - "Unexpected keyword arguments: {}".format(", ".join(sorted(kwargs))) - ) - - -class LsofFdLeakChecker(object): - def get_open_files(self): - out = self._exec_lsof() - open_files = self._parse_lsof_output(out) - return open_files - - def _exec_lsof(self): - pid = os.getpid() - # py3: use subprocess.DEVNULL directly. - with open(os.devnull, "wb") as devnull: - return subprocess.check_output( - ("lsof", "-Ffn0", "-p", str(pid)), stderr=devnull - ).decode() +class LsofFdLeakChecker: + def get_open_files(self) -> List[Tuple[str, str]]: + out = subprocess.run( + ("lsof", "-Ffn0", "-p", str(os.getpid())), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True, + universal_newlines=True, + ).stdout - def _parse_lsof_output(self, out): - def isopen(line): + def isopen(line: str) -> bool: return line.startswith("f") and ( "deleted" not in line and "mem" not in line @@ -121,16 +130,16 @@ class LsofFdLeakChecker(object): return open_files - def matching_platform(self): + def matching_platform(self) -> bool: try: - subprocess.check_output(("lsof", "-v")) + subprocess.run(("lsof", "-v"), check=True) except (OSError, subprocess.CalledProcessError): return False else: return True @pytest.hookimpl(hookwrapper=True, tryfirst=True) - def pytest_runtest_protocol(self, item): + def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]: lines1 = self.get_open_files() yield if hasattr(sys, "pypy_version_info"): @@ -140,16 +149,17 @@ class LsofFdLeakChecker(object): new_fds = {t[0] for t in lines2} - {t[0] for t in lines1} leaked_files = [t for t in lines2 if t[0] in new_fds] if leaked_files: - error = [] - error.append("***** %s FD leakage detected" % len(leaked_files)) - error.extend([str(f) for f in leaked_files]) - error.append("*** Before:") - error.extend([str(f) for f in lines1]) - error.append("*** After:") - error.extend([str(f) for f in lines2]) - error.append(error[0]) - error.append("*** function %s:%s: %s " % item.location) - error.append("See issue #2366") + error = [ + "***** %s FD leakage detected" % len(leaked_files), + *(str(f) for f in leaked_files), + "*** Before:", + *(str(f) for f in lines1), + "*** After:", + *(str(f) for f in lines2), + "***** %s FD leakage detected" % len(leaked_files), + "*** function %s:%s: %s " % item.location, + "See issue #2366", + ] item.warn(pytest.PytestWarning("\n".join(error))) @@ -157,70 +167,73 @@ class LsofFdLeakChecker(object): @pytest.fixture -def _pytest(request): +def _pytest(request: FixtureRequest) -> "PytestArg": """Return a helper which offers a gethookrecorder(hook) method which returns a HookRecorder instance which helps to make assertions about called - hooks. - - """ + hooks.""" return PytestArg(request) -class PytestArg(object): - def __init__(self, request): +class PytestArg: + def __init__(self, request: FixtureRequest) -> None: self.request = request - def gethookrecorder(self, hook): + def gethookrecorder(self, hook) -> "HookRecorder": hookrecorder = HookRecorder(hook._pm) self.request.addfinalizer(hookrecorder.finish_recording) return hookrecorder -def get_public_names(values): +def get_public_names(values: Iterable[str]) -> List[str]: """Only return names from iterator values without a leading underscore.""" return [x for x in values if x[0] != "_"] -class ParsedCall(object): - def __init__(self, name, kwargs): +class ParsedCall: + def __init__(self, name: str, kwargs) -> None: self.__dict__.update(kwargs) self._name = name - def __repr__(self): + def __repr__(self) -> str: d = self.__dict__.copy() del d["_name"] - return "<ParsedCall %r(**%r)>" % (self._name, d) + return "<ParsedCall {!r}(**{!r})>".format(self._name, d) + + if TYPE_CHECKING: + # The class has undetermined attributes, this tells mypy about it. + def __getattr__(self, key: str): + ... -class HookRecorder(object): +class HookRecorder: """Record all hooks called in a plugin manager. This wraps all the hook calls in the plugin manager, recording each call before propagating the normal calls. - """ - def __init__(self, pluginmanager): + def __init__(self, pluginmanager: PytestPluginManager) -> None: self._pluginmanager = pluginmanager - self.calls = [] + self.calls = [] # type: List[ParsedCall] + self.ret = None # type: Optional[Union[int, ExitCode]] - def before(hook_name, hook_impls, kwargs): + def before(hook_name: str, hook_impls, kwargs) -> None: self.calls.append(ParsedCall(hook_name, kwargs)) - def after(outcome, hook_name, hook_impls, kwargs): + def after(outcome, hook_name: str, hook_impls, kwargs) -> None: pass self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after) - def finish_recording(self): + def finish_recording(self) -> None: self._undo_wrapping() - def getcalls(self, names): + def getcalls(self, names: Union[str, Iterable[str]]) -> List[ParsedCall]: if isinstance(names, str): names = names.split() return [call for call in self.calls if call._name in names] - def assert_contains(self, entries): + def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) @@ -239,35 +252,66 @@ class HookRecorder(object): break print("NONAMEMATCH", name, "with", call) else: - pytest.fail("could not find %r check %r" % (name, check)) + pytest.fail("could not find {!r} check {!r}".format(name, check)) - def popcall(self, name): + def popcall(self, name: str) -> ParsedCall: __tracebackhide__ = True for i, call in enumerate(self.calls): if call._name == name: del self.calls[i] return call - lines = ["could not find call %r, in:" % (name,)] + lines = ["could not find call {!r}, in:".format(name)] lines.extend([" %s" % x for x in self.calls]) pytest.fail("\n".join(lines)) - def getcall(self, name): + def getcall(self, name: str) -> ParsedCall: values = self.getcalls(name) assert len(values) == 1, (name, values) return values[0] # functionality for test reports - def getreports(self, names="pytest_runtest_logreport pytest_collectreport"): + @overload + def getreports( + self, names: "Literal['pytest_collectreport']", + ) -> Sequence[CollectReport]: + ... + + @overload # noqa: F811 + def getreports( # noqa: F811 + self, names: "Literal['pytest_runtest_logreport']", + ) -> Sequence[TestReport]: + ... + + @overload # noqa: F811 + def getreports( # noqa: F811 + self, + names: Union[str, Iterable[str]] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[Union[CollectReport, TestReport]]: + ... + + def getreports( # noqa: F811 + self, + names: Union[str, Iterable[str]] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[Union[CollectReport, TestReport]]: return [x.report for x in self.getcalls(names)] def matchreport( self, - inamepart="", - names="pytest_runtest_logreport pytest_collectreport", - when=None, - ): - """return a testreport whose dotted import path matches""" + inamepart: str = "", + names: Union[str, Iterable[str]] = ( + "pytest_runtest_logreport", + "pytest_collectreport", + ), + when: Optional[str] = None, + ) -> Union[CollectReport, TestReport]: + """Return a testreport whose dotted import path matches.""" values = [] for rep in self.getreports(names=names): if not when and rep.when != "call" and rep.passed: @@ -284,23 +328,62 @@ class HookRecorder(object): ) if len(values) > 1: raise ValueError( - "found 2 or more testreports matching %r: %s" % (inamepart, values) + "found 2 or more testreports matching {!r}: {}".format( + inamepart, values + ) ) return values[0] - def getfailures(self, names="pytest_runtest_logreport pytest_collectreport"): + @overload + def getfailures( + self, names: "Literal['pytest_collectreport']", + ) -> Sequence[CollectReport]: + ... + + @overload # noqa: F811 + def getfailures( # noqa: F811 + self, names: "Literal['pytest_runtest_logreport']", + ) -> Sequence[TestReport]: + ... + + @overload # noqa: F811 + def getfailures( # noqa: F811 + self, + names: Union[str, Iterable[str]] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[Union[CollectReport, TestReport]]: + ... + + def getfailures( # noqa: F811 + self, + names: Union[str, Iterable[str]] = ( + "pytest_collectreport", + "pytest_runtest_logreport", + ), + ) -> Sequence[Union[CollectReport, TestReport]]: return [rep for rep in self.getreports(names) if rep.failed] - def getfailedcollections(self): + def getfailedcollections(self) -> Sequence[CollectReport]: return self.getfailures("pytest_collectreport") - def listoutcomes(self): + def listoutcomes( + self, + ) -> Tuple[ + Sequence[TestReport], + Sequence[Union[CollectReport, TestReport]], + Sequence[Union[CollectReport, TestReport]], + ]: passed = [] skipped = [] failed = [] - for rep in self.getreports("pytest_collectreport pytest_runtest_logreport"): + for rep in self.getreports( + ("pytest_collectreport", "pytest_runtest_logreport") + ): if rep.passed: if rep.when == "call": + assert isinstance(rep, TestReport) passed.append(rep) elif rep.skipped: skipped.append(rep) @@ -309,36 +392,55 @@ class HookRecorder(object): failed.append(rep) return passed, skipped, failed - def countoutcomes(self): + def countoutcomes(self) -> List[int]: return [len(x) for x in self.listoutcomes()] - def assertoutcome(self, passed=0, skipped=0, failed=0): - realpassed, realskipped, realfailed = self.listoutcomes() - assert passed == len(realpassed) - assert skipped == len(realskipped) - assert failed == len(realfailed) + def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None: + __tracebackhide__ = True + + outcomes = self.listoutcomes() + realpassed, realskipped, realfailed = outcomes + obtained = { + "passed": len(realpassed), + "skipped": len(realskipped), + "failed": len(realfailed), + } + expected = {"passed": passed, "skipped": skipped, "failed": failed} + assert obtained == expected, outcomes - def clear(self): + def clear(self) -> None: self.calls[:] = [] @pytest.fixture -def linecomp(request): +def linecomp() -> "LineComp": + """A :class: `LineComp` instance for checking that an input linearly + contains a sequence of strings.""" return LineComp() @pytest.fixture(name="LineMatcher") -def LineMatcher_fixture(request): +def LineMatcher_fixture(request: FixtureRequest) -> "Type[LineMatcher]": + """A reference to the :class: `LineMatcher`. + + This is instantiable with a list of lines (without their trailing newlines). + This is useful for testing large texts, such as the output of commands. + """ return LineMatcher @pytest.fixture -def testdir(request, tmpdir_factory): +def testdir(request: FixtureRequest, tmpdir_factory: TempdirFactory) -> "Testdir": + """A :class: `TestDir` instance, that can be used to run and test pytest itself. + + It is particularly useful for testing plugins. It is similar to the `tmpdir` fixture + but provides methods which aid in testing pytest itself. + """ return Testdir(request, tmpdir_factory) @pytest.fixture -def _sys_snapshot(): +def _sys_snapshot() -> Generator[None, None, None]: snappaths = SysPathsSnapshot() snapmods = SysModulesSnapshot() yield @@ -347,7 +449,7 @@ def _sys_snapshot(): @pytest.fixture -def _config_for_test(): +def _config_for_test() -> Generator[Config, None, None]: from _pytest.config import get_config config = get_config() @@ -355,67 +457,103 @@ def _config_for_test(): config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles. -rex_outcome = re.compile(r"(\d+) ([\w-]+)") +# Regex to match the session duration string in the summary: "74.34s". +rex_session_duration = re.compile(r"\d+\.\d\ds") +# Regex to match all the counts and phrases in the summary line: "34 passed, 111 skipped". +rex_outcome = re.compile(r"(\d+) (\w+)") -class RunResult(object): - """The result of running a command. - - Attributes: - - :ret: the return value - :outlines: list of lines captured from stdout - :errlines: list of lines captures from stderr - :stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to - reconstruct stdout or the commonly used ``stdout.fnmatch_lines()`` - method - :stderr: :py:class:`LineMatcher` of stderr - :duration: duration in seconds +class RunResult: + """The result of running a command.""" - """ - - def __init__(self, ret, outlines, errlines, duration): - self.ret = ret + def __init__( + self, + ret: Union[int, ExitCode], + outlines: List[str], + errlines: List[str], + duration: float, + ) -> None: + try: + self.ret = pytest.ExitCode(ret) # type: Union[int, ExitCode] + """The return value.""" + except ValueError: + self.ret = ret self.outlines = outlines + """List of lines captured from stdout.""" self.errlines = errlines + """List of lines captured from stderr.""" self.stdout = LineMatcher(outlines) + """:class:`LineMatcher` of stdout. + + Use e.g. :func:`stdout.str() <LineMatcher.str()>` to reconstruct stdout, or the commonly used + :func:`stdout.fnmatch_lines() <LineMatcher.fnmatch_lines()>` method. + """ self.stderr = LineMatcher(errlines) + """:class:`LineMatcher` of stderr.""" self.duration = duration + """Duration in seconds.""" - def __repr__(self): + def __repr__(self) -> str: return ( - "<RunResult ret=%r len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>" + "<RunResult ret=%s len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>" % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) ) - def parseoutcomes(self): - """Return a dictionary of outcomestring->num from parsing the terminal + def parseoutcomes(self) -> Dict[str, int]: + """Return a dictionary of outcome noun -> count from parsing the terminal output that the test process produced. + The returned nouns will always be in plural form:: + + ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== + + Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. + """ + return self.parse_summary_nouns(self.outlines) + + @classmethod + def parse_summary_nouns(cls, lines) -> Dict[str, int]: + """Extract the nouns from a pytest terminal summary line. + + It always returns the plural noun for consistency:: + + ======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ==== + + Will return ``{"failed": 1, "passed": 1, "warnings": 1, "errors": 1}``. """ - for line in reversed(self.outlines): - if "seconds" in line: + for line in reversed(lines): + if rex_session_duration.search(line): outcomes = rex_outcome.findall(line) - if outcomes: - d = {} - for num, cat in outcomes: - d[cat] = int(num) - return d - raise ValueError("Pytest terminal report not found") + ret = {noun: int(count) for (count, noun) in outcomes} + break + else: + raise ValueError("Pytest terminal summary report not found") + + to_plural = { + "warning": "warnings", + "error": "errors", + } + return {to_plural.get(k, k): v for k, v in ret.items()} def assert_outcomes( - self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=0 - ): + self, + passed: int = 0, + skipped: int = 0, + failed: int = 0, + errors: int = 0, + xpassed: int = 0, + xfailed: int = 0, + ) -> None: """Assert that the specified outcomes appear with the respective - numbers (0 means it didn't occur) in the text output from a test run. + numbers (0 means it didn't occur) in the text output from a test run.""" + __tracebackhide__ = True - """ d = self.parseoutcomes() obtained = { "passed": d.get("passed", 0), "skipped": d.get("skipped", 0), "failed": d.get("failed", 0), - "error": d.get("error", 0), + "errors": d.get("errors", 0), "xpassed": d.get("xpassed", 0), "xfailed": d.get("xfailed", 0), } @@ -423,27 +561,27 @@ class RunResult(object): "passed": passed, "skipped": skipped, "failed": failed, - "error": error, + "errors": errors, "xpassed": xpassed, "xfailed": xfailed, } assert obtained == expected -class CwdSnapshot(object): - def __init__(self): +class CwdSnapshot: + def __init__(self) -> None: self.__saved = os.getcwd() - def restore(self): + def restore(self) -> None: os.chdir(self.__saved) -class SysModulesSnapshot(object): - def __init__(self, preserve=None): +class SysModulesSnapshot: + def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: self.__preserve = preserve self.__saved = dict(sys.modules) - def restore(self): + def restore(self) -> None: if self.__preserve: self.__saved.update( (k, m) for k, m in sys.modules.items() if self.__preserve(k) @@ -452,54 +590,59 @@ class SysModulesSnapshot(object): sys.modules.update(self.__saved) -class SysPathsSnapshot(object): - def __init__(self): +class SysPathsSnapshot: + def __init__(self) -> None: self.__saved = list(sys.path), list(sys.meta_path) - def restore(self): + def restore(self) -> None: sys.path[:], sys.meta_path[:] = self.__saved -class Testdir(object): +@final +class Testdir: """Temporary test directory with tools to test/run pytest itself. - This is based on the ``tmpdir`` fixture but provides a number of methods + This is based on the :fixture:`tmpdir` fixture but provides a number of methods which aid with testing pytest itself. Unless :py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as their current working directory. Attributes: - :tmpdir: The :py:class:`py.path.local` instance of the temporary directory. + :ivar tmpdir: The :py:class:`py.path.local` instance of the temporary directory. - :plugins: A list of plugins to use with :py:meth:`parseconfig` and + :ivar plugins: + A list of plugins to use with :py:meth:`parseconfig` and :py:meth:`runpytest`. Initially this is an empty list but plugins can be added to the list. The type of items to add to the list depends on the method using them so refer to them for details. - """ + __test__ = False + CLOSE_STDIN = object class TimeoutExpired(Exception): pass - def __init__(self, request, tmpdir_factory): + def __init__(self, request: FixtureRequest, tmpdir_factory: TempdirFactory) -> None: self.request = request - self._mod_collections = WeakKeyDictionary() - name = request.function.__name__ + self._mod_collections = ( + WeakKeyDictionary() + ) # type: WeakKeyDictionary[Module, List[Union[Item, Collector]]] + if request.function: + name = request.function.__name__ # type: str + else: + name = request.node.name + self._name = name self.tmpdir = tmpdir_factory.mktemp(name, numbered=True) self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True) - self.plugins = [] + self.plugins = [] # type: List[Union[str, _PluggyPlugin]] self._cwd_snapshot = CwdSnapshot() self._sys_path_snapshot = SysPathsSnapshot() self._sys_modules_snapshot = self.__take_sys_modules_snapshot() self.chdir() self.request.addfinalizer(self.finalize) - method = self.request.config.getoption("--runpytest") - if method == "inprocess": - self._runpytest_method = self.runpytest_inprocess - elif method == "subprocess": - self._runpytest_method = self.runpytest_subprocess + self._method = self.request.config.getoption("--runpytest") mp = self.monkeypatch = MonkeyPatch() mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self.test_tmproot)) @@ -507,84 +650,87 @@ class Testdir(object): mp.delenv("TOX_ENV_DIR", raising=False) # Discard outer pytest options. mp.delenv("PYTEST_ADDOPTS", raising=False) - - # Environment (updates) for inner runs. + # Ensure no user config is used. tmphome = str(self.tmpdir) - self._env_run_update = {"HOME": tmphome, "USERPROFILE": tmphome} + mp.setenv("HOME", tmphome) + mp.setenv("USERPROFILE", tmphome) + # Do not use colors for inner runs by default. + mp.setenv("PY_COLORS", "0") - def __repr__(self): - return "<Testdir %r>" % (self.tmpdir,) + def __repr__(self) -> str: + return "<Testdir {!r}>".format(self.tmpdir) - def __str__(self): + def __str__(self) -> str: return str(self.tmpdir) - def finalize(self): + def finalize(self) -> None: """Clean up global state artifacts. Some methods modify the global interpreter state and this tries to clean this up. It does not remove the temporary directory however so it can be looked at after the test run has finished. - """ self._sys_modules_snapshot.restore() self._sys_path_snapshot.restore() self._cwd_snapshot.restore() self.monkeypatch.undo() - def __take_sys_modules_snapshot(self): - # some zope modules used by twisted-related tests keep internal state + def __take_sys_modules_snapshot(self) -> SysModulesSnapshot: + # Some zope modules used by twisted-related tests keep internal state # and can't be deleted; we had some trouble in the past with - # `zope.interface` for example + # `zope.interface` for example. def preserve_module(name): return name.startswith("zope") return SysModulesSnapshot(preserve=preserve_module) - def make_hook_recorder(self, pluginmanager): + def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder: """Create a new :py:class:`HookRecorder` for a PluginManager.""" pluginmanager.reprec = reprec = HookRecorder(pluginmanager) self.request.addfinalizer(reprec.finish_recording) return reprec - def chdir(self): + def chdir(self) -> None: """Cd into the temporary directory. This is done automatically upon instantiation. - """ self.tmpdir.chdir() - def _makefile(self, ext, args, kwargs, encoding="utf-8"): - items = list(kwargs.items()) + def _makefile(self, ext: str, lines, files, encoding: str = "utf-8"): + items = list(files.items()) def to_text(s): - return s.decode(encoding) if isinstance(s, bytes) else six.text_type(s) + return s.decode(encoding) if isinstance(s, bytes) else str(s) - if args: - source = u"\n".join(to_text(x) for x in args) - basename = self.request.function.__name__ + if lines: + source = "\n".join(to_text(x) for x in lines) + basename = self._name items.insert(0, (basename, source)) ret = None for basename, value in items: p = self.tmpdir.join(basename).new(ext=ext) p.dirpath().ensure_dir() - source = Source(value) - source = u"\n".join(to_text(line) for line in source.lines) + source_ = Source(value) + source = "\n".join(to_text(line) for line in source_.lines) p.write(source.strip().encode(encoding), "wb") if ret is None: ret = p return ret - def makefile(self, ext, *args, **kwargs): + def makefile(self, ext: str, *args: str, **kwargs): r"""Create new file(s) in the testdir. - :param str ext: The extension the file(s) should use, including the dot, e.g. `.py`. - :param list[str] args: All args will be treated as strings and joined using newlines. - The result will be written as contents to the file. The name of the - file will be based on the test function requesting this fixture. - :param kwargs: Each keyword is the name of a file, while the value of it will - be written as contents of the file. + :param str ext: + The extension the file(s) should use, including the dot, e.g. `.py`. + :param args: + All args are treated as strings and joined using newlines. + The result is written as contents to the file. The name of the + file is based on the test function requesting this fixture. + :param kwargs: + Each keyword is the name of a file, while the value of it will + be written as contents of the file. Examples: @@ -605,20 +751,59 @@ class Testdir(object): """Write a tox.ini file with 'source' as contents.""" return self.makefile(".ini", tox=source) - def getinicfg(self, source): + def getinicfg(self, source) -> IniConfig: """Return the pytest section from the tox.ini config file.""" p = self.makeini(source) - return py.iniconfig.IniConfig(p)["pytest"] + return IniConfig(p)["pytest"] + + def makepyprojecttoml(self, source): + """Write a pyproject.toml file with 'source' as contents. + + .. versionadded:: 6.0 + """ + return self.makefile(".toml", pyproject=source) def makepyfile(self, *args, **kwargs): - """Shortcut for .makefile() with a .py extension.""" + r"""Shortcut for .makefile() with a .py extension. + + Defaults to the test name with a '.py' extension, e.g test_foobar.py, overwriting + existing files. + + Examples: + + .. code-block:: python + + def test_something(testdir): + # Initial file is created test_something.py. + testdir.makepyfile("foobar") + # To create multiple files, pass kwargs accordingly. + testdir.makepyfile(custom="foobar") + # At this point, both 'test_something.py' & 'custom.py' exist in the test directory. + + """ return self._makefile(".py", args, kwargs) def maketxtfile(self, *args, **kwargs): - """Shortcut for .makefile() with a .txt extension.""" + r"""Shortcut for .makefile() with a .txt extension. + + Defaults to the test name with a '.txt' extension, e.g test_foobar.txt, overwriting + existing files. + + Examples: + + .. code-block:: python + + def test_something(testdir): + # Initial file is created test_something.txt. + testdir.maketxtfile("foobar") + # To create multiple files, pass kwargs accordingly. + testdir.maketxtfile(custom="foobar") + # At this point, both 'test_something.txt' & 'custom.txt' exist in the test directory. + + """ return self._makefile(".txt", args, kwargs) - def syspathinsert(self, path=None): + def syspathinsert(self, path=None) -> None: """Prepend a directory to sys.path, defaults to :py:attr:`tmpdir`. This is undone automatically when this object dies at the end of each @@ -629,22 +814,26 @@ class Testdir(object): self.monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name): + def mkdir(self, name) -> py.path.local: """Create a new (sub)directory.""" return self.tmpdir.mkdir(name) - def mkpydir(self, name): - """Create a new python package. + def mkpydir(self, name) -> py.path.local: + """Create a new Python package. This creates a (sub)directory with an empty ``__init__.py`` file so it - gets recognised as a python package. - + gets recognised as a Python package. """ p = self.mkdir(name) p.ensure("__init__.py") return p - def copy_example(self, name=None): + def copy_example(self, name=None) -> py.path.local: + """Copy file from project's directory into the testdir. + + :param str name: The name of the file to copy. + :returns: Path to the copied directory (inside ``self.tmpdir``). + """ import warnings from _pytest.warning_types import PYTESTER_COPY_EXAMPLE @@ -659,7 +848,7 @@ class Testdir(object): example_dir = example_dir.join(*extra_element.args) if name is None: - func_name = self.request.function.__name__ + func_name = self._name maybe_dir = example_dir / func_name maybe_file = example_dir / (func_name + ".py") @@ -690,22 +879,21 @@ class Testdir(object): Session = Session - def getnode(self, config, arg): + def getnode(self, config: Config, arg): """Return the collection node of a file. - :param config: :py:class:`_pytest.config.Config` instance, see - :py:meth:`parseconfig` and :py:meth:`parseconfigure` to create the - configuration - - :param arg: a :py:class:`py.path.local` instance of the file - + :param _pytest.config.Config config: + A pytest config. + See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it. + :param py.path.local arg: + Path to the file. """ - session = Session(config) + session = Session.from_config(config) assert "::" not in str(arg) p = py.path.local(arg) config.hook.pytest_sessionstart(session=session) res = session.perform_collect([str(p)], genitems=False)[0] - config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK) + config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res def getpathnode(self, path): @@ -714,26 +902,24 @@ class Testdir(object): This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to create the (configured) pytest Config instance. - :param path: a :py:class:`py.path.local` instance of the file - + :param py.path.local path: Path to the file. """ config = self.parseconfigure(path) - session = Session(config) + session = Session.from_config(config) x = session.fspath.bestrelpath(path) config.hook.pytest_sessionstart(session=session) res = session.perform_collect([x], genitems=False)[0] - config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK) + config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def genitems(self, colitems): + def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: """Generate all test items from a collection node. This recurses into the collection node and returns a list of all the test items contained within. - """ session = colitems[0].session - result = [] + result = [] # type: List[Item] for colitem in colitems: result.extend(session.genitems(colitem)) return result @@ -745,7 +931,6 @@ class Testdir(object): provide a ``.getrunner()`` method which should return a runner which can run the test protocol for a single item, e.g. :py:func:`_pytest.runner.runtestprotocol`. - """ # used from runner functional tests item = self.getitem(source) @@ -754,37 +939,37 @@ class Testdir(object): runner = testclassinstance.getrunner() return runner(item) - def inline_runsource(self, source, *cmdlineargs): + def inline_runsource(self, source, *cmdlineargs) -> HookRecorder: """Run a test module in process using ``pytest.main()``. This run writes "source" into a temporary file and runs ``pytest.main()`` on it, returning a :py:class:`HookRecorder` instance for the result. - :param source: the source code of the test module + :param source: The source code of the test module. - :param cmdlineargs: any extra command line arguments to use - - :return: :py:class:`HookRecorder` instance of the result + :param cmdlineargs: Any extra command line arguments to use. + :returns: :py:class:`HookRecorder` instance of the result. """ p = self.makepyfile(source) values = list(cmdlineargs) + [p] return self.inline_run(*values) - def inline_genitems(self, *args): + def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: """Run ``pytest.main(['--collectonly'])`` in-process. Runs the :py:func:`pytest.main` function to run all of pytest inside the test process itself like :py:meth:`inline_run`, but returns a tuple of the collected items and a :py:class:`HookRecorder` instance. - """ rec = self.inline_run("--collect-only", *args) items = [x.item for x in rec.getcalls("pytest_itemcollected")] return items, rec - def inline_run(self, *args, **kwargs): + def inline_run( + self, *args, plugins=(), no_reraise_ctrlc: bool = False + ) -> HookRecorder: """Run ``pytest.main()`` in-process, returning a HookRecorder. Runs the :py:func:`pytest.main` function to run all of pytest inside @@ -793,37 +978,24 @@ class Testdir(object): from that run than can be done by matching stdout/stderr from :py:meth:`runpytest`. - :param args: command line arguments to pass to :py:func:`pytest.main` - - :param plugins: (keyword-only) extra plugin instances the - ``pytest.main()`` instance should use + :param args: + Command line arguments to pass to :py:func:`pytest.main`. + :param plugins: + Extra plugin instances the ``pytest.main()`` instance should use. + :param no_reraise_ctrlc: + Typically we reraise keyboard interrupts from the child run. If + True, the KeyboardInterrupt exception is captured. - :return: a :py:class:`HookRecorder` instance + :returns: A :py:class:`HookRecorder` instance. """ - plugins = kwargs.pop("plugins", []) - no_reraise_ctrlc = kwargs.pop("no_reraise_ctrlc", None) - raise_on_kwargs(kwargs) + # (maybe a cpython bug?) the importlib cache sometimes isn't updated + # properly between file creation and inline_run (especially if imports + # are interspersed with file creation) + importlib.invalidate_caches() + plugins = list(plugins) finalizers = [] try: - # Do not load user config (during runs only). - mp_run = MonkeyPatch() - for k, v in self._env_run_update.items(): - mp_run.setenv(k, v) - finalizers.append(mp_run.undo) - - # When running pytest inline any plugins active in the main test - # process are already imported. So this disables the warning which - # will trigger to say they can no longer be rewritten, which is - # fine as they have already been rewritten. - orig_warn = AssertionRewritingHook._warn_already_imported - - def revert_warn_already_imported(): - AssertionRewritingHook._warn_already_imported = orig_warn - - finalizers.append(revert_warn_already_imported) - AssertionRewritingHook._warn_already_imported = lambda *a: None - # Any sys.module or sys.path changes done while running pytest # inline should be reverted after the test run completes to avoid # clashing with later inline tests run within the same pytest test, @@ -839,8 +1011,8 @@ class Testdir(object): rec = [] - class Collect(object): - def pytest_configure(x, config): + class Collect: + def pytest_configure(x, config: Config) -> None: rec.append(self.make_hook_recorder(config.pluginmanager)) plugins.append(Collect()) @@ -849,14 +1021,14 @@ class Testdir(object): reprec = rec.pop() else: - class reprec(object): + class reprec: # type: ignore pass reprec.ret = ret - # typically we reraise keyboard interrupts from the child run - # because it's our user requesting interruption of the testing - if ret == EXIT_INTERRUPTED and not no_reraise_ctrlc: + # Typically we reraise keyboard interrupts from the child run + # because it's our user requesting interruption of the testing. + if ret == ExitCode.INTERRUPTED and not no_reraise_ctrlc: calls = reprec.getcalls("pytest_keyboard_interrupt") if calls and calls[-1].excinfo.type == KeyboardInterrupt: raise KeyboardInterrupt() @@ -865,30 +1037,34 @@ class Testdir(object): for finalizer in finalizers: finalizer() - def runpytest_inprocess(self, *args, **kwargs): + def runpytest_inprocess(self, *args, **kwargs) -> RunResult: """Return result of running pytest in-process, providing a similar - interface to what self.runpytest() provides. - """ + interface to what self.runpytest() provides.""" syspathinsert = kwargs.pop("syspathinsert", False) if syspathinsert: self.syspathinsert() - now = time.time() - capture = MultiCapture(Capture=SysCapture) + now = timing.time() + capture = _get_multicapture("sys") capture.start_capturing() try: try: reprec = self.inline_run(*args, **kwargs) except SystemExit as e: + ret = e.args[0] + try: + ret = ExitCode(e.args[0]) + except ValueError: + pass - class reprec(object): - ret = e.args[0] + class reprec: # type: ignore + ret = ret except Exception: traceback.print_exc() - class reprec(object): - ret = 3 + class reprec: # type: ignore + ret = ExitCode(3) finally: out, err = capture.readouterr() @@ -896,28 +1072,33 @@ class Testdir(object): sys.stdout.write(out) sys.stderr.write(err) - res = RunResult(reprec.ret, out.split("\n"), err.split("\n"), time.time() - now) - res.reprec = reprec + assert reprec.ret is not None + res = RunResult( + reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now + ) + res.reprec = reprec # type: ignore return res - def runpytest(self, *args, **kwargs): + def runpytest(self, *args, **kwargs) -> RunResult: """Run pytest inline or in a subprocess, depending on the command line - option "--runpytest" and return a :py:class:`RunResult`. - - """ + option "--runpytest" and return a :py:class:`RunResult`.""" args = self._ensure_basetemp(args) - return self._runpytest_method(*args, **kwargs) + if self._method == "inprocess": + return self.runpytest_inprocess(*args, **kwargs) + elif self._method == "subprocess": + return self.runpytest_subprocess(*args, **kwargs) + raise RuntimeError("Unrecognized runpytest option: {}".format(self._method)) def _ensure_basetemp(self, args): args = list(args) for x in args: - if safe_str(x).startswith("--basetemp"): + if str(x).startswith("--basetemp"): break else: args.append("--basetemp=%s" % self.tmpdir.dirpath("basetemp")) return args - def parseconfig(self, *args): + def parseconfig(self, *args) -> Config: """Return a new pytest Config instance from given commandline args. This invokes the pytest bootstrapping code in _pytest.config to create @@ -927,107 +1108,102 @@ class Testdir(object): If :py:attr:`plugins` has been populated they should be plugin modules to be registered with the PluginManager. - """ args = self._ensure_basetemp(args) import _pytest.config - config = _pytest.config._prepareconfig(args, self.plugins) + config = _pytest.config._prepareconfig(args, self.plugins) # type: ignore[arg-type] # we don't know what the test will do with this half-setup config # object and thus we make sure it gets unconfigured properly in any # case (otherwise capturing could still be active, for example) self.request.addfinalizer(config._ensure_unconfigure) return config - def parseconfigure(self, *args): + def parseconfigure(self, *args) -> Config: """Return a new pytest configured Config instance. - This returns a new :py:class:`_pytest.config.Config` instance like + Returns a new :py:class:`_pytest.config.Config` instance like :py:meth:`parseconfig`, but also calls the pytest_configure hook. - """ config = self.parseconfig(*args) config._do_configure() - self.request.addfinalizer(config._ensure_unconfigure) return config - def getitem(self, source, funcname="test_func"): + def getitem(self, source, funcname: str = "test_func") -> Item: """Return the test item for a test function. - This writes the source to a python file and runs pytest's collection on + Writes the source to a python file and runs pytest's collection on the resulting module, returning the test item for the requested function name. - :param source: the module source - - :param funcname: the name of the test function for which to return a - test item - + :param source: + The module source. + :param funcname: + The name of the test function for which to return a test item. """ items = self.getitems(source) for item in items: if item.name == funcname: return item - assert 0, "%r item not found in module:\n%s\nitems: %s" % ( - funcname, - source, - items, + assert 0, "{!r} item not found in module:\n{}\nitems: {}".format( + funcname, source, items ) - def getitems(self, source): + def getitems(self, source) -> List[Item]: """Return all test items collected from the module. - This writes the source to a python file and runs pytest's collection on + Writes the source to a Python file and runs pytest's collection on the resulting module, returning all test items contained within. - """ modcol = self.getmodulecol(source) return self.genitems([modcol]) - def getmodulecol(self, source, configargs=(), withinit=False): + def getmodulecol(self, source, configargs=(), withinit: bool = False): """Return the module collection node for ``source``. - This writes ``source`` to a file using :py:meth:`makepyfile` and then + Writes ``source`` to a file using :py:meth:`makepyfile` and then runs the pytest collection on it, returning the collection node for the test module. - :param source: the source code of the module to collect - - :param configargs: any extra arguments to pass to - :py:meth:`parseconfigure` + :param source: + The source code of the module to collect. - :param withinit: whether to also write an ``__init__.py`` file to the - same directory to ensure it is a package + :param configargs: + Any extra arguments to pass to :py:meth:`parseconfigure`. + :param withinit: + Whether to also write an ``__init__.py`` file to the same + directory to ensure it is a package. """ if isinstance(source, Path): path = self.tmpdir.join(str(source)) assert not withinit, "not supported for paths" else: - kw = {self.request.function.__name__: Source(source).strip()} + kw = {self._name: Source(source).strip()} path = self.makepyfile(**kw) if withinit: self.makepyfile(__init__="#") self.config = config = self.parseconfigure(path, *configargs) return self.getnode(config, path) - def collect_by_name(self, modcol, name): + def collect_by_name( + self, modcol: Module, name: str + ) -> Optional[Union[Item, Collector]]: """Return the collection node for name from the module collection. - This will search a module collection node for a collection node - matching the given name. - - :param modcol: a module collection node; see :py:meth:`getmodulecol` - - :param name: the name of the node to return + Searchs a module collection node for a collection node matching the + given name. + :param modcol: A module collection node; see :py:meth:`getmodulecol`. + :param name: The name of the node to return. """ if modcol not in self._mod_collections: self._mod_collections[modcol] = list(modcol.collect()) for colitem in self._mod_collections[modcol]: if colitem.name == name: return colitem + return None def popen( self, @@ -1039,17 +1215,15 @@ class Testdir(object): ): """Invoke subprocess.Popen. - This calls subprocess.Popen making sure the current working directory - is in the PYTHONPATH. + Calls subprocess.Popen making sure the current working directory is + in the PYTHONPATH. You probably want to use :py:meth:`run` instead. - """ env = os.environ.copy() env["PYTHONPATH"] = os.pathsep.join( filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) ) - env.update(self._env_run_update) kw["env"] = env if stdin is Testdir.CLOSE_STDIN: @@ -1061,45 +1235,47 @@ class Testdir(object): popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) if stdin is Testdir.CLOSE_STDIN: + assert popen.stdin is not None popen.stdin.close() elif isinstance(stdin, bytes): + assert popen.stdin is not None popen.stdin.write(stdin) return popen - def run(self, *cmdargs, **kwargs): + def run( + self, *cmdargs, timeout: Optional[float] = None, stdin=CLOSE_STDIN + ) -> RunResult: """Run a command with arguments. Run a process using subprocess.Popen saving the stdout and stderr. - :param args: the sequence of arguments to pass to `subprocess.Popen()` - :param timeout: the period in seconds after which to timeout and raise - :py:class:`Testdir.TimeoutExpired` - :param stdin: optional standard input. Bytes are being send, closing + :param args: + The sequence of arguments to pass to `subprocess.Popen()`. + :param timeout: + The period in seconds after which to timeout and raise + :py:class:`Testdir.TimeoutExpired`. + :param stdin: + Optional standard input. Bytes are being send, closing the pipe, otherwise it is passed through to ``popen``. Defaults to ``CLOSE_STDIN``, which translates to using a pipe (``subprocess.PIPE``) that gets closed. - Returns a :py:class:`RunResult`. - + :rtype: RunResult """ __tracebackhide__ = True - timeout = kwargs.pop("timeout", None) - stdin = kwargs.pop("stdin", Testdir.CLOSE_STDIN) - raise_on_kwargs(kwargs) - - cmdargs = [ + cmdargs = tuple( str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs - ] + ) p1 = self.tmpdir.join("stdout") p2 = self.tmpdir.join("stderr") print("running:", *cmdargs) print(" in:", py.path.local()) - f1 = codecs.open(str(p1), "w", encoding="utf8") - f2 = codecs.open(str(p2), "w", encoding="utf8") + f1 = open(str(p1), "w", encoding="utf8") + f2 = open(str(p2), "w", encoding="utf8") try: - now = time.time() + now = timing.time() popen = self.popen( cmdargs, stdin=stdin, @@ -1110,7 +1286,7 @@ class Testdir(object): if isinstance(stdin, bytes): popen.stdin.close() - def handle_timeout(): + def handle_timeout() -> None: __tracebackhide__ = True timeout_message = ( @@ -1124,30 +1300,16 @@ class Testdir(object): if timeout is None: ret = popen.wait() - elif not six.PY2: + else: try: ret = popen.wait(timeout) except subprocess.TimeoutExpired: handle_timeout() - else: - end = time.time() + timeout - - resolution = min(0.1, timeout / 10) - - while True: - ret = popen.poll() - if ret is not None: - break - - if time.time() > end: - handle_timeout() - - time.sleep(resolution) finally: f1.close() f2.close() - f1 = codecs.open(str(p1), "r", encoding="utf8") - f2 = codecs.open(str(p2), "r", encoding="utf8") + f1 = open(str(p1), encoding="utf8") + f2 = open(str(p2), encoding="utf8") try: out = f1.read().splitlines() err = f2.read().splitlines() @@ -1156,31 +1318,37 @@ class Testdir(object): f2.close() self._dump_lines(out, sys.stdout) self._dump_lines(err, sys.stderr) - return RunResult(ret, out, err, time.time() - now) + try: + ret = ExitCode(ret) + except ValueError: + pass + return RunResult(ret, out, err, timing.time() - now) def _dump_lines(self, lines, fp): try: for line in lines: print(line, file=fp) except UnicodeEncodeError: - print("couldn't print to %s because of encoding" % (fp,)) + print("couldn't print to {} because of encoding".format(fp)) - def _getpytestargs(self): + def _getpytestargs(self) -> Tuple[str, ...]: return sys.executable, "-mpytest" - def runpython(self, script): + def runpython(self, script) -> RunResult: """Run a python script using sys.executable as interpreter. - Returns a :py:class:`RunResult`. - + :rtype: RunResult """ return self.run(sys.executable, script) def runpython_c(self, command): - """Run python -c "command", return a :py:class:`RunResult`.""" + """Run python -c "command". + + :rtype: RunResult + """ return self.run(sys.executable, "-c", command) - def runpytest_subprocess(self, *args, **kwargs): + def runpytest_subprocess(self, *args, timeout: Optional[float] = None) -> RunResult: """Run pytest as a subprocess with given arguments. Any plugins added to the :py:attr:`plugins` list will be added using the @@ -1189,19 +1357,16 @@ class Testdir(object): with "runpytest-" to not conflict with the normal numbered pytest location for temporary files and directories. - :param args: the sequence of arguments to pass to the pytest subprocess - :param timeout: the period in seconds after which to timeout and raise - :py:class:`Testdir.TimeoutExpired` + :param args: + The sequence of arguments to pass to the pytest subprocess. + :param timeout: + The period in seconds after which to timeout and raise + :py:class:`Testdir.TimeoutExpired`. - Returns a :py:class:`RunResult`. + :rtype: RunResult """ __tracebackhide__ = True - timeout = kwargs.pop("timeout", None) - raise_on_kwargs(kwargs) - - p = py.path.local.make_numbered_dir( - prefix="runpytest-", keep=None, rootdir=self.tmpdir - ) + p = make_numbered_dir(root=Path(str(self.tmpdir)), prefix="runpytest-") args = ("--basetemp=%s" % p,) + args plugins = [x for x in self.plugins if isinstance(x, str)] if plugins: @@ -1209,69 +1374,58 @@ class Testdir(object): args = self._getpytestargs() + args return self.run(*args, timeout=timeout) - def spawn_pytest(self, string, expect_timeout=10.0): + def spawn_pytest( + self, string: str, expect_timeout: float = 10.0 + ) -> "pexpect.spawn": """Run pytest using pexpect. This makes sure to use the right pytest and sets up the temporary directory locations. The pexpect child is returned. - """ basetemp = self.tmpdir.mkdir("temp-pexpect") invoke = " ".join(map(str, self._getpytestargs())) - cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string) + cmd = "{} --basetemp={} {}".format(invoke, basetemp, string) return self.spawn(cmd, expect_timeout=expect_timeout) - def spawn(self, cmd, expect_timeout=10.0): + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": """Run a command using pexpect. The pexpect child is returned. - """ pexpect = pytest.importorskip("pexpect", "3.0") if hasattr(sys, "pypy_version_info") and "64" in platform.machine(): pytest.skip("pypy-64 bit not supported") - if sys.platform.startswith("freebsd"): - pytest.xfail("pexpect does not work reliably on freebsd") + if not hasattr(pexpect, "spawn"): + pytest.skip("pexpect.spawn not available") logfile = self.tmpdir.join("spawn.out").open("wb") - # Do not load user config. - env = os.environ.copy() - env.update(self._env_run_update) - - child = pexpect.spawn(cmd, logfile=logfile, env=env) + child = pexpect.spawn(cmd, logfile=logfile) self.request.addfinalizer(logfile.close) child.timeout = expect_timeout return child -def getdecoded(out): - try: - return out.decode("utf-8") - except UnicodeDecodeError: - return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (saferepr(out),) - +class LineComp: + def __init__(self) -> None: + self.stringio = StringIO() + """:class:`python:io.StringIO()` instance used for input.""" -class LineComp(object): - def __init__(self): - self.stringio = py.io.TextIO() - - def assert_contains_lines(self, lines2): - """Assert that lines2 are contained (linearly) in lines1. - - Return a list of extralines found. + def assert_contains_lines(self, lines2: Sequence[str]) -> None: + """Assert that ``lines2`` are contained (linearly) in :attr:`stringio`'s value. + Lines are matched using :func:`LineMatcher.fnmatch_lines`. """ __tracebackhide__ = True val = self.stringio.getvalue() self.stringio.truncate(0) self.stringio.seek(0) lines1 = val.split("\n") - return LineMatcher(lines1).fnmatch_lines(lines2) + LineMatcher(lines1).fnmatch_lines(lines2) -class LineMatcher(object): +class LineMatcher: """Flexible matching of text. This is a convenience class to test large texts like the output of @@ -1279,49 +1433,33 @@ class LineMatcher(object): The constructor takes a list of lines without their trailing newlines, i.e. ``text.splitlines()``. - """ - def __init__(self, lines): + def __init__(self, lines: List[str]) -> None: self.lines = lines - self._log_output = [] - - def str(self): - """Return the entire original text.""" - return "\n".join(self.lines) + self._log_output = [] # type: List[str] - def _getlines(self, lines2): + def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]: if isinstance(lines2, str): lines2 = Source(lines2) if isinstance(lines2, Source): lines2 = lines2.strip().lines return lines2 - def fnmatch_lines_random(self, lines2): - """Check lines exist in the output using in any order. - - Lines are checked using ``fnmatch.fnmatch``. The argument is a list of - lines which have to occur in the output, in any order. - - """ + def fnmatch_lines_random(self, lines2: Sequence[str]) -> None: + """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`).""" + __tracebackhide__ = True self._match_lines_random(lines2, fnmatch) - def re_match_lines_random(self, lines2): - """Check lines exist in the output using ``re.match``, in any order. - - The argument is a list of lines which have to occur in the output, in - any order. - - """ - self._match_lines_random(lines2, lambda name, pat: re.match(pat, name)) - - def _match_lines_random(self, lines2, match_func): - """Check lines exist in the output. - - The argument is a list of lines which have to occur in the output, in - any order. Each line can contain glob whildcards. + def re_match_lines_random(self, lines2: Sequence[str]) -> None: + """Check lines exist in the output in any order (using :func:`python:re.match`).""" + __tracebackhide__ = True + self._match_lines_random(lines2, lambda name, pat: bool(re.match(pat, name))) - """ + def _match_lines_random( + self, lines2: Sequence[str], match_func: Callable[[str, str], bool] + ) -> None: + __tracebackhide__ = True lines2 = self._getlines(lines2) for line in lines2: for x in self.lines: @@ -1329,85 +1467,178 @@ class LineMatcher(object): self._log("matched: ", repr(line)) break else: - self._log("line %r not found in output" % line) - raise ValueError(self._log_text) + msg = "line %r not found in output" % line + self._log(msg) + self._fail(msg) - def get_lines_after(self, fnline): + def get_lines_after(self, fnline: str) -> Sequence[str]: """Return all lines following the given line in the text. The given line can contain glob wildcards. - """ for i, line in enumerate(self.lines): if fnline == line or fnmatch(line, fnline): return self.lines[i + 1 :] raise ValueError("line %r not found in output" % fnline) - def _log(self, *args): + def _log(self, *args) -> None: self._log_output.append(" ".join(str(x) for x in args)) @property - def _log_text(self): + def _log_text(self) -> str: return "\n".join(self._log_output) - def fnmatch_lines(self, lines2): - """Search captured text for matching lines using ``fnmatch.fnmatch``. + def fnmatch_lines( + self, lines2: Sequence[str], *, consecutive: bool = False + ) -> None: + """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`). The argument is a list of lines which have to match and can use glob wildcards. If they do not match a pytest.fail() is called. The - matches and non-matches are also printed on stdout. + matches and non-matches are also shown as part of the error message. + :param lines2: String patterns to match. + :param consecutive: Match lines consecutively? """ __tracebackhide__ = True - self._match_lines(lines2, fnmatch, "fnmatch") + self._match_lines(lines2, fnmatch, "fnmatch", consecutive=consecutive) - def re_match_lines(self, lines2): - """Search captured text for matching lines using ``re.match``. + def re_match_lines( + self, lines2: Sequence[str], *, consecutive: bool = False + ) -> None: + """Check lines exist in the output (using :func:`python:re.match`). The argument is a list of lines which have to match using ``re.match``. If they do not match a pytest.fail() is called. - The matches and non-matches are also printed on stdout. + The matches and non-matches are also shown as part of the error message. + :param lines2: string patterns to match. + :param consecutive: match lines consecutively? """ __tracebackhide__ = True - self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match") + self._match_lines( + lines2, + lambda name, pat: bool(re.match(pat, name)), + "re.match", + consecutive=consecutive, + ) - def _match_lines(self, lines2, match_func, match_nickname): + def _match_lines( + self, + lines2: Sequence[str], + match_func: Callable[[str, str], bool], + match_nickname: str, + *, + consecutive: bool = False + ) -> None: """Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``. - :param list[str] lines2: list of string patterns to match. The actual - format depends on ``match_func`` - :param match_func: a callable ``match_func(line, pattern)`` where line - is the captured line from stdout/stderr and pattern is the matching - pattern - :param str match_nickname: the nickname for the match function that - will be logged to stdout when a match occurs - + :param Sequence[str] lines2: + List of string patterns to match. The actual format depends on + ``match_func``. + :param match_func: + A callable ``match_func(line, pattern)`` where line is the + captured line from stdout/stderr and pattern is the matching + pattern. + :param str match_nickname: + The nickname for the match function that will be logged to stdout + when a match occurs. + :param consecutive: + Match lines consecutively? """ - assert isinstance(lines2, Sequence) + if not isinstance(lines2, collections.abc.Sequence): + raise TypeError("invalid type for lines2: {}".format(type(lines2).__name__)) lines2 = self._getlines(lines2) lines1 = self.lines[:] nextline = None extralines = [] __tracebackhide__ = True + wnick = len(match_nickname) + 1 + started = False for line in lines2: nomatchprinted = False while lines1: nextline = lines1.pop(0) if line == nextline: self._log("exact match:", repr(line)) + started = True break elif match_func(nextline, line): self._log("%s:" % match_nickname, repr(line)) - self._log(" with:", repr(nextline)) + self._log( + "{:>{width}}".format("with:", width=wnick), repr(nextline) + ) + started = True break else: + if consecutive and started: + msg = "no consecutive match: {!r}".format(line) + self._log(msg) + self._log( + "{:>{width}}".format("with:", width=wnick), repr(nextline) + ) + self._fail(msg) if not nomatchprinted: - self._log("nomatch:", repr(line)) + self._log( + "{:>{width}}".format("nomatch:", width=wnick), repr(line) + ) nomatchprinted = True - self._log(" and:", repr(nextline)) + self._log("{:>{width}}".format("and:", width=wnick), repr(nextline)) extralines.append(nextline) else: - self._log("remains unmatched: %r" % (line,)) - pytest.fail(self._log_text) + msg = "remains unmatched: {!r}".format(line) + self._log(msg) + self._fail(msg) + self._log_output = [] + + def no_fnmatch_line(self, pat: str) -> None: + """Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``. + + :param str pat: The pattern to match lines. + """ + __tracebackhide__ = True + self._no_match_line(pat, fnmatch, "fnmatch") + + def no_re_match_line(self, pat: str) -> None: + """Ensure captured lines do not match the given pattern, using ``re.match``. + + :param str pat: The regular expression to match lines. + """ + __tracebackhide__ = True + self._no_match_line( + pat, lambda name, pat: bool(re.match(pat, name)), "re.match" + ) + + def _no_match_line( + self, pat: str, match_func: Callable[[str, str], bool], match_nickname: str + ) -> None: + """Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``. + + :param str pat: The pattern to match lines. + """ + __tracebackhide__ = True + nomatch_printed = False + wnick = len(match_nickname) + 1 + for line in self.lines: + if match_func(line, pat): + msg = "{}: {!r}".format(match_nickname, pat) + self._log(msg) + self._log("{:>{width}}".format("with:", width=wnick), repr(line)) + self._fail(msg) + else: + if not nomatch_printed: + self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat)) + nomatch_printed = True + self._log("{:>{width}}".format("and:", width=wnick), repr(line)) + self._log_output = [] + + def _fail(self, msg: str) -> None: + __tracebackhide__ = True + log_text = self._log_text + self._log_output = [] + pytest.fail(log_text) + + def str(self) -> str: + """Return the entire original text.""" + return "\n".join(self.lines) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py index 5f1e6885be3..7d3e301c076 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python.py @@ -1,67 +1,84 @@ -# -*- coding: utf-8 -*- -""" Python test discovery, setup and run of test functions. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import collections +"""Python test discovery, setup and run of test functions.""" +import enum import fnmatch import inspect +import itertools import os import sys +import types +import typing import warnings +from collections import Counter +from collections import defaultdict +from collections.abc import Sequence from functools import partial -from textwrap import dedent +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Mapping +from typing import Optional +from typing import Set +from typing import Tuple +from typing import Union import py -import six import _pytest -from _pytest import deprecated from _pytest import fixtures from _pytest import nodes from _pytest._code import filter_traceback +from _pytest._code import getfslineno +from _pytest._code.code import ExceptionInfo +from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter +from _pytest._io.saferepr import saferepr from _pytest.compat import ascii_escaped -from _pytest.compat import enum +from _pytest.compat import final from _pytest.compat import get_default_arg_names from _pytest.compat import get_real_func -from _pytest.compat import getfslineno from _pytest.compat import getimfunc from _pytest.compat import getlocation +from _pytest.compat import is_async_function from _pytest.compat import is_generator -from _pytest.compat import isclass -from _pytest.compat import isfunction from _pytest.compat import NOTSET from _pytest.compat import REGEX_TYPE from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass -from _pytest.compat import safe_str from _pytest.compat import STRING_TYPES +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config import ExitCode from _pytest.config import hookimpl -from _pytest.main import FSHookProxy +from _pytest.config.argparsing import Parser +from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH +from _pytest.fixtures import FuncFixtureInfo +from _pytest.main import Session from _pytest.mark import MARK_GEN +from _pytest.mark import ParameterSet from _pytest.mark.structures import get_unpacked_marks +from _pytest.mark.structures import Mark +from _pytest.mark.structures import MarkDecorator from _pytest.mark.structures import normalize_mark_list from _pytest.outcomes import fail from _pytest.outcomes import skip +from _pytest.pathlib import import_path +from _pytest.pathlib import ImportPathMismatchError from _pytest.pathlib import parts +from _pytest.pathlib import visit from _pytest.warning_types import PytestCollectionWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning - -def pyobj_property(name): - def get(self): - node = self.getparent(getattr(__import__("pytest"), name)) - if node is not None: - return node.obj - - doc = "python %s object this node was collected from (can be None)." % ( - name.lower(), - ) - return property(get, None, None, doc) +if TYPE_CHECKING: + from typing import Type + from typing_extensions import Literal + from _pytest.fixtures import _Scope -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--fixtures", @@ -106,38 +123,24 @@ def pytest_addoption(parser): "side effects(use at your own risk)", ) - group.addoption( - "--import-mode", - default="prepend", - choices=["prepend", "append"], - dest="importmode", - help="prepend/append to sys.path when importing test modules, " - "default is to prepend.", - ) - -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.showfixtures: showfixtures(config) return 0 if config.option.show_fixtures_per_test: show_fixtures_per_test(config) return 0 + return None -def pytest_generate_tests(metafunc): - # those alternative spellings are common - raise a specific error to alert - # the user - alt_spellings = ["parameterize", "parametrise", "parameterise"] - for mark_name in alt_spellings: - if metafunc.definition.get_closest_marker(mark_name): - msg = "{0} has '{1}' mark, spelling should be 'parametrize'" - fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False) +def pytest_generate_tests(metafunc: "Metafunc") -> None: for marker in metafunc.definition.iter_markers(name="parametrize"): - metafunc.parametrize(*marker.args, **marker.kwargs) + # TODO: Fix this type-ignore (overlapping kwargs). + metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) # type: ignore[misc] -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.addinivalue_line( "markers", "parametrize(argnames, argvalues): call a test function multiple " @@ -146,75 +149,88 @@ def pytest_configure(config): "or a list of tuples of values if argnames specifies multiple names. " "Example: @parametrize('arg1', [1,2]) would lead to two calls of the " "decorated test function, one with arg1=1 and another with arg1=2." - "see https://docs.pytest.org/en/latest/parametrize.html for more info " + "see https://docs.pytest.org/en/stable/parametrize.html for more info " "and examples.", ) config.addinivalue_line( "markers", "usefixtures(fixturename1, fixturename2, ...): mark tests as needing " "all of the specified fixtures. see " - "https://docs.pytest.org/en/latest/fixture.html#usefixtures ", + "https://docs.pytest.org/en/stable/fixture.html#usefixtures ", + ) + + +def async_warn_and_skip(nodeid: str) -> None: + msg = "async def functions are not natively supported and have been skipped.\n" + msg += ( + "You need to install a suitable plugin for your async framework, for example:\n" ) + msg += " - anyio\n" + msg += " - pytest-asyncio\n" + msg += " - pytest-tornasync\n" + msg += " - pytest-trio\n" + msg += " - pytest-twisted" + warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid))) + skip(msg="async def function and no async plugin installed (see warnings)") @hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem): +def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: testfunction = pyfuncitem.obj - iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None) - if iscoroutinefunction is not None and iscoroutinefunction(testfunction): - msg = "Coroutine functions are not natively supported and have been skipped.\n" - msg += "You need to install a suitable plugin for your async framework, for example:\n" - msg += " - pytest-asyncio\n" - msg += " - pytest-trio\n" - msg += " - pytest-tornasync" - warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid))) - skip(msg="coroutine function and no async plugin installed (see warnings)") + if is_async_function(testfunction): + async_warn_and_skip(pyfuncitem.nodeid) funcargs = pyfuncitem.funcargs testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} - testfunction(**testargs) + result = testfunction(**testargs) + if hasattr(result, "__await__") or hasattr(result, "__aiter__"): + async_warn_and_skip(pyfuncitem.nodeid) return True -def pytest_collect_file(path, parent): +def pytest_collect_file( + path: py.path.local, parent: nodes.Collector +) -> Optional["Module"]: ext = path.ext if ext == ".py": if not parent.session.isinitpath(path): if not path_matches_patterns( path, parent.config.getini("python_files") + ["__init__.py"] ): - return + return None ihook = parent.session.gethookproxy(path) - return ihook.pytest_pycollect_makemodule(path=path, parent=parent) + module = ihook.pytest_pycollect_makemodule( + path=path, parent=parent + ) # type: Module + return module + return None -def path_matches_patterns(path, patterns): - """Returns True if the given py.path.local matches one of the patterns in the list of globs given""" +def path_matches_patterns(path: py.path.local, patterns: Iterable[str]) -> bool: + """Return whether path matches any of the patterns in the list of globs given.""" return any(path.fnmatch(pattern) for pattern in patterns) -def pytest_pycollect_makemodule(path, parent): +def pytest_pycollect_makemodule(path: py.path.local, parent) -> "Module": if path.basename == "__init__.py": - return Package(path, parent) - return Module(path, parent) + pkg = Package.from_parent(parent, fspath=path) # type: Package + return pkg + mod = Module.from_parent(parent, fspath=path) # type: Module + return mod -@hookimpl(hookwrapper=True) -def pytest_pycollect_makeitem(collector, name, obj): - outcome = yield - res = outcome.get_result() - if res is not None: - return - # nothing was collected elsewhere, let's do it here +@hookimpl(trylast=True) +def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object): + # Nothing was collected elsewhere, let's do it here. if safe_isclass(obj): if collector.istestclass(obj, name): - outcome.force_result(Class(name, parent=collector)) + return Class.from_parent(collector, name=name, obj=obj) elif collector.istestfunction(obj, name): - # mock seems to store unbound methods (issue473), normalize it + # mock seems to store unbound methods (issue473), normalize it. obj = getattr(obj, "__func__", obj) # We need to try and unwrap the function if it's a functools.partial - # or a funtools.wrapped. - # We musn't if it's been wrapped with mock.patch (python 2 only) - if not (isfunction(obj) or isfunction(get_real_func(obj))): + # or a functools.wrapped. + # We mustn't if it's been wrapped with mock.patch (python 2 only). + if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))): filename, lineno = getfslineno(obj) warnings.warn_explicit( message=PytestCollectionWarning( @@ -226,30 +242,49 @@ def pytest_pycollect_makeitem(collector, name, obj): ) elif getattr(obj, "__test__", True): if is_generator(obj): - res = Function(name, parent=collector) - reason = deprecated.YIELD_TESTS.format(name=name) + res = Function.from_parent(collector, name=name) + reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format( + name=name + ) res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) res.warn(PytestCollectionWarning(reason)) else: res = list(collector._genfunctions(name, obj)) - outcome.force_result(res) + return res -def pytest_make_parametrize_id(config, val, argname=None): - return None +class PyobjMixin: + _ALLOW_MARKERS = True + # Function and attributes that the mixin needs (for type-checking only). + if TYPE_CHECKING: + name = "" # type: str + parent = None # type: Optional[nodes.Node] + own_markers = [] # type: List[Mark] -class PyobjContext(object): - module = pyobj_property("Module") - cls = pyobj_property("Class") - instance = pyobj_property("Instance") + def getparent(self, cls: Type[nodes._NodeType]) -> Optional[nodes._NodeType]: + ... + def listchain(self) -> List[nodes.Node]: + ... -class PyobjMixin(PyobjContext): - _ALLOW_MARKERS = True + @property + def module(self): + """Python module object this node was collected from (can be None).""" + node = self.getparent(Module) + return node.obj if node is not None else None - def __init__(self, *k, **kw): - super(PyobjMixin, self).__init__(*k, **kw) + @property + def cls(self): + """Python class object this node was collected from (can be None).""" + node = self.getparent(Class) + return node.obj if node is not None else None + + @property + def instance(self): + """Python instance object this node was collected from (can be None).""" + node = self.getparent(Instance) + return node.obj if node is not None else None @property def obj(self): @@ -268,11 +303,14 @@ class PyobjMixin(PyobjContext): self._obj = value def _getobj(self): - """Gets the underlying Python object. May be overwritten by subclasses.""" - return getattr(self.parent.obj, self.name) - - def getmodpath(self, stopatmodule=True, includemodule=False): - """ return python path relative to the containing module. """ + """Get the underlying Python object. May be overwritten by subclasses.""" + # TODO: Improve the type of `parent` such that assert/ignore aren't needed. + assert self.parent is not None + obj = self.parent.obj # type: ignore[attr-defined] + return getattr(obj, self.name) + + def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str: + """Return Python path relative to the containing module.""" chain = self.listchain() chain.reverse() parts = [] @@ -288,18 +326,18 @@ class PyobjMixin(PyobjContext): break parts.append(name) parts.reverse() - s = ".".join(parts) - return s.replace(".[", "[") + return ".".join(parts) - def reportinfo(self): + def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]: # XXX caching? obj = self.obj compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) if isinstance(compat_co_firstlineno, int): # nose compatibility - fspath = sys.modules[obj.__module__].__file__ - if fspath.endswith(".pyc"): - fspath = fspath[:-1] + file_path = sys.modules[obj.__module__].__file__ + if file_path.endswith(".pyc"): + file_path = file_path[:-1] + fspath = file_path # type: Union[py.path.local, str] lineno = compat_co_firstlineno else: fspath, lineno = getfslineno(obj) @@ -308,26 +346,46 @@ class PyobjMixin(PyobjContext): return fspath, lineno, modpath +# As an optimization, these builtin attribute names are pre-ignored when +# iterating over an object during collection -- the pytest_pycollect_makeitem +# hook is not called for them. +# fmt: off +class _EmptyClass: pass # noqa: E701 +IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305 + frozenset(), + # Module. + dir(types.ModuleType("empty_module")), + # Some extra module attributes the above doesn't catch. + {"__builtins__", "__file__", "__cached__"}, + # Class. + dir(_EmptyClass), + # Instance. + dir(_EmptyClass()), +) +del _EmptyClass +# fmt: on + + class PyCollector(PyobjMixin, nodes.Collector): - def funcnamefilter(self, name): + def funcnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_functions", name) - def isnosetest(self, obj): - """ Look for the __test__ attribute, which is applied by the - @nose.tools.istest decorator + def isnosetest(self, obj: object) -> bool: + """Look for the __test__ attribute, which is applied by the + @nose.tools.istest decorator. """ # We explicitly check for "is True" here to not mistakenly treat # classes with a custom __getattr__ returning something truthy (like a # function) as test classes. return safe_getattr(obj, "__test__", False) is True - def classnamefilter(self, name): + def classnamefilter(self, name: str) -> bool: return self._matches_prefix_or_glob_option("python_classes", name) - def istestfunction(self, obj, name): + def istestfunction(self, obj: object, name: str) -> bool: if self.funcnamefilter(name) or self.isnosetest(obj): if isinstance(obj, staticmethod): - # static methods need to be unwrapped + # staticmethods need to be unwrapped. obj = safe_getattr(obj, "__func__", False) return ( safe_getattr(obj, "__call__", False) @@ -336,63 +394,72 @@ class PyCollector(PyobjMixin, nodes.Collector): else: return False - def istestclass(self, obj, name): + def istestclass(self, obj: object, name: str) -> bool: return self.classnamefilter(name) or self.isnosetest(obj) - def _matches_prefix_or_glob_option(self, option_name, name): - """ - checks if the given name matches the prefix or glob-pattern defined - in ini configuration. - """ + def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: + """Check if the given name matches the prefix or glob-pattern defined + in ini configuration.""" for option in self.config.getini(option_name): if name.startswith(option): return True - # check that name looks like a glob-string before calling fnmatch + # Check that name looks like a glob-string before calling fnmatch # because this is called for every name in each collected module, - # and fnmatch is somewhat expensive to call + # and fnmatch is somewhat expensive to call. elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch( name, option ): return True return False - def collect(self): + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: if not getattr(self.obj, "__test__", True): return [] # NB. we avoid random getattrs and peek in the __dict__ instead # (XXX originally introduced from a PyPy need, still true?) dicts = [getattr(self.obj, "__dict__", {})] - for basecls in inspect.getmro(self.obj.__class__): + for basecls in self.obj.__class__.__mro__: dicts.append(basecls.__dict__) - seen = {} - values = [] + seen = set() # type: Set[str] + values = [] # type: List[Union[nodes.Item, nodes.Collector]] + ihook = self.ihook for dic in dicts: + # Note: seems like the dict can change during iteration - + # be careful not to remove the list() without consideration. for name, obj in list(dic.items()): + if name in IGNORED_ATTRIBUTES: + continue if name in seen: continue - seen[name] = True - res = self._makeitem(name, obj) + seen.add(name) + res = ihook.pytest_pycollect_makeitem( + collector=self, name=name, obj=obj + ) if res is None: continue - if not isinstance(res, list): - res = [res] - values.extend(res) - values.sort(key=lambda item: item.reportinfo()[:2]) - return values + elif isinstance(res, list): + values.extend(res) + else: + values.append(res) - def _makeitem(self, name, obj): - # assert self.ihook.fspath == self.fspath, self - return self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj) + def sort_key(item): + fspath, lineno, _ = item.reportinfo() + return (str(fspath), lineno) - def _genfunctions(self, name, funcobj): - module = self.getparent(Module).obj + values.sort(key=sort_key) + return values + + def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: + modulecol = self.getparent(Module) + assert modulecol is not None + module = modulecol.obj clscol = self.getparent(Class) cls = clscol and clscol.obj or None fm = self.session._fixturemanager - definition = FunctionDefinition(name=name, parent=self, callobj=funcobj) - fixtureinfo = fm.getfixtureinfo(definition, funcobj, cls) + definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj) + fixtureinfo = definition._fixtureinfo metafunc = Metafunc( definition, fixtureinfo, self.config, cls=cls, module=module @@ -400,31 +467,27 @@ class PyCollector(PyobjMixin, nodes.Collector): methods = [] if hasattr(module, "pytest_generate_tests"): methods.append(module.pytest_generate_tests) - if hasattr(cls, "pytest_generate_tests"): + if cls is not None and hasattr(cls, "pytest_generate_tests"): methods.append(cls().pytest_generate_tests) - if methods: - self.ihook.pytest_generate_tests.call_extra( - methods, dict(metafunc=metafunc) - ) - else: - self.ihook.pytest_generate_tests(metafunc=metafunc) + + self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc)) if not metafunc._calls: - yield Function(name, parent=self, fixtureinfo=fixtureinfo) + yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo) else: - # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs + # Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs. fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm) - # add_funcarg_pseudo_fixture_def may have shadowed some fixtures + # Add_funcarg_pseudo_fixture_def may have shadowed some fixtures # with direct parametrization, so make sure we update what the # function really needs. fixtureinfo.prune_dependency_tree() for callspec in metafunc._calls: - subname = "%s[%s]" % (name, callspec.id) - yield Function( + subname = "{}[{}]".format(name, callspec.id) + yield Function.from_parent( + self, name=subname, - parent=self, callspec=callspec, callobj=funcobj, fixtureinfo=fixtureinfo, @@ -434,37 +497,36 @@ class PyCollector(PyobjMixin, nodes.Collector): class Module(nodes.File, PyCollector): - """ Collector for test classes and functions. """ + """Collector for test classes and functions.""" def _getobj(self): return self._importtestmodule() - def collect(self): + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: self._inject_setup_module_fixture() self._inject_setup_function_fixture() self.session._fixturemanager.parsefactories(self) - return super(Module, self).collect() + return super().collect() - def _inject_setup_module_fixture(self): - """Injects a hidden autouse, module scoped fixture into the collected module object + def _inject_setup_module_fixture(self) -> None: + """Inject a hidden autouse, module scoped fixture into the collected module object that invokes setUpModule/tearDownModule if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ - setup_module = _get_non_fixture_func(self.obj, "setUpModule") - if setup_module is None: - setup_module = _get_non_fixture_func(self.obj, "setup_module") - - teardown_module = _get_non_fixture_func(self.obj, "tearDownModule") - if teardown_module is None: - teardown_module = _get_non_fixture_func(self.obj, "teardown_module") + setup_module = _get_first_non_fixture_func( + self.obj, ("setUpModule", "setup_module") + ) + teardown_module = _get_first_non_fixture_func( + self.obj, ("tearDownModule", "teardown_module") + ) if setup_module is None and teardown_module is None: return @fixtures.fixture(autouse=True, scope="module") - def xunit_setup_module_fixture(request): + def xunit_setup_module_fixture(request) -> Generator[None, None, None]: if setup_module is not None: _call_with_optional_argument(setup_module, request.module) yield @@ -473,20 +535,22 @@ class Module(nodes.File, PyCollector): self.obj.__pytest_setup_module = xunit_setup_module_fixture - def _inject_setup_function_fixture(self): - """Injects a hidden autouse, function scoped fixture into the collected module object + def _inject_setup_function_fixture(self) -> None: + """Inject a hidden autouse, function scoped fixture into the collected module object that invokes setup_function/teardown_function if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ - setup_function = _get_non_fixture_func(self.obj, "setup_function") - teardown_function = _get_non_fixture_func(self.obj, "teardown_function") + setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",)) + teardown_function = _get_first_non_fixture_func( + self.obj, ("teardown_function",) + ) if setup_function is None and teardown_function is None: return @fixtures.fixture(autouse=True, scope="function") - def xunit_setup_function_fixture(request): + def xunit_setup_function_fixture(request) -> Generator[None, None, None]: if request.instance is not None: # in this case we are bound to an instance, so we need to let # setup_method handle this @@ -501,16 +565,15 @@ class Module(nodes.File, PyCollector): self.obj.__pytest_setup_function = xunit_setup_function_fixture def _importtestmodule(self): - # we assume we are only called once per module + # We assume we are only called once per module. importmode = self.config.getoption("--import-mode") try: - mod = self.fspath.pyimport(ensuresyspath=importmode) - except SyntaxError: + mod = import_path(self.fspath, mode=importmode) + except SyntaxError as e: raise self.CollectError( - _pytest._code.ExceptionInfo.from_current().getrepr(style="short") - ) - except self.fspath.ImportMismatchError: - e = sys.exc_info()[1] + ExceptionInfo.from_current().getrepr(style="short") + ) from e + except ImportPathMismatchError as e: raise self.CollectError( "import file mismatch:\n" "imported module %r has this __file__ attribute:\n" @@ -519,10 +582,8 @@ class Module(nodes.File, PyCollector): " %s\n" "HINT: remove __pycache__ / .pyc files and/or use a " "unique basename for your test file modules" % e.args - ) - except ImportError: - from _pytest._code.code import ExceptionInfo - + ) from e + except ImportError as e: exc_info = ExceptionInfo.from_current() if self.config.getoption("verbose") < 2: exc_info.traceback = exc_info.traceback.filter(filter_traceback) @@ -531,14 +592,14 @@ class Module(nodes.File, PyCollector): if exc_info.traceback else exc_info.exconly() ) - formatted_tb = safe_str(exc_repr) + formatted_tb = str(exc_repr) raise self.CollectError( "ImportError while importing test module '{fspath}'.\n" "Hint: make sure your test modules/packages have valid Python names.\n" "Traceback:\n" "{traceback}".format(fspath=self.fspath, traceback=formatted_tb) - ) - except _pytest.runner.Skipped as e: + ) from e + except skip.Exception as e: if e.allow_module_level: raise raise self.CollectError( @@ -546,74 +607,75 @@ class Module(nodes.File, PyCollector): "To decorate a test function, use the @pytest.mark.skip " "or @pytest.mark.skipif decorators instead, and to skip a " "module use `pytestmark = pytest.mark.{skip,skipif}." - ) + ) from e self.config.pluginmanager.consider_module(mod) return mod class Package(Module): - def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None): + def __init__( + self, + fspath: py.path.local, + parent: nodes.Collector, + # NOTE: following args are unused: + config=None, + session=None, + nodeid=None, + ) -> None: + # NOTE: Could be just the following, but kept as-is for compat. + # nodes.FSCollector.__init__(self, fspath, parent=parent) session = parent.session nodes.FSCollector.__init__( self, fspath, parent=parent, config=config, session=session, nodeid=nodeid ) - self.name = fspath.dirname - self.trace = session.trace - self._norecursepatterns = session._norecursepatterns - self.fspath = fspath - - def setup(self): - # not using fixtures to call setup_module here because autouse fixtures - # from packages are not called automatically (#4085) - setup_module = _get_non_fixture_func(self.obj, "setUpModule") - if setup_module is None: - setup_module = _get_non_fixture_func(self.obj, "setup_module") + self.name = os.path.basename(str(fspath.dirname)) + + def setup(self) -> None: + # Not using fixtures to call setup_module here because autouse fixtures + # from packages are not called automatically (#4085). + setup_module = _get_first_non_fixture_func( + self.obj, ("setUpModule", "setup_module") + ) if setup_module is not None: _call_with_optional_argument(setup_module, self.obj) - teardown_module = _get_non_fixture_func(self.obj, "tearDownModule") - if teardown_module is None: - teardown_module = _get_non_fixture_func(self.obj, "teardown_module") + teardown_module = _get_first_non_fixture_func( + self.obj, ("tearDownModule", "teardown_module") + ) if teardown_module is not None: func = partial(_call_with_optional_argument, teardown_module, self.obj) self.addfinalizer(func) - def _recurse(self, dirpath): - if dirpath.basename == "__pycache__": + def gethookproxy(self, fspath: py.path.local): + warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) + return self.session.gethookproxy(fspath) + + def isinitpath(self, path: py.path.local) -> bool: + warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) + return self.session.isinitpath(path) + + def _recurse(self, direntry: "os.DirEntry[str]") -> bool: + if direntry.name == "__pycache__": + return False + path = py.path.local(direntry.path) + ihook = self.session.gethookproxy(path.dirpath()) + if ihook.pytest_ignore_collect(path=path, config=self.config): + return False + norecursepatterns = self.config.getini("norecursedirs") + if any(path.check(fnmatch=pat) for pat in norecursepatterns): return False - ihook = self.gethookproxy(dirpath.dirpath()) - if ihook.pytest_ignore_collect(path=dirpath, config=self.config): - return - for pat in self._norecursepatterns: - if dirpath.check(fnmatch=pat): - return False - ihook = self.gethookproxy(dirpath) - ihook.pytest_collect_directory(path=dirpath, parent=self) return True - def gethookproxy(self, fspath): - # check if we have the common case of running - # hooks with all conftest.py filesall conftest.py - pm = self.config.pluginmanager - my_conftestmodules = pm._getconftestmodules(fspath) - remove_mods = pm._conftest_plugins.difference(my_conftestmodules) - if remove_mods: - # one or more conftests are not in use at this fspath - proxy = FSHookProxy(fspath, pm, remove_mods) - else: - # all plugis are active for this fspath - proxy = self.config.hook - return proxy - - def _collectfile(self, path, handle_dupes=True): - assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % ( - path, - path.isdir(), - path.exists(), - path.islink(), + def _collectfile( + self, path: py.path.local, handle_dupes: bool = True + ) -> typing.Sequence[nodes.Collector]: + assert ( + path.isfile() + ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format( + path, path.isdir(), path.exists(), path.islink() ) - ihook = self.gethookproxy(path) - if not self.isinitpath(path): + ihook = self.session.gethookproxy(path) + if not self.session.isinitpath(path): if ihook.pytest_ignore_collect(path=path, config=self.config): return () @@ -626,70 +688,43 @@ class Package(Module): else: duplicate_paths.add(path) - if self.fspath == path: # __init__.py - return [self] - - return ihook.pytest_collect_file(path=path, parent=self) - - def isinitpath(self, path): - return path in self.session._initialpaths + return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return] - def collect(self): + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: this_path = self.fspath.dirpath() init_module = this_path.join("__init__.py") if init_module.check(file=1) and path_matches_patterns( init_module, self.config.getini("python_files") ): - yield Module(init_module, self) - pkg_prefixes = set() - for path in this_path.visit(rec=self._recurse, bf=True, sort=True): + yield Module.from_parent(self, fspath=init_module) + pkg_prefixes = set() # type: Set[py.path.local] + for direntry in visit(str(this_path), recurse=self._recurse): + path = py.path.local(direntry.path) + # We will visit our own __init__.py file, in which case we skip it. - is_file = path.isfile() - if is_file: - if path.basename == "__init__.py" and path.dirpath() == this_path: + if direntry.is_file(): + if direntry.name == "__init__.py" and path.dirpath() == this_path: continue - parts_ = parts(path.strpath) + parts_ = parts(direntry.path) if any( - pkg_prefix in parts_ and pkg_prefix.join("__init__.py") != path + str(pkg_prefix) in parts_ and pkg_prefix.join("__init__.py") != path for pkg_prefix in pkg_prefixes ): continue - if is_file: - for x in self._collectfile(path): - yield x - elif not path.isdir(): + if direntry.is_file(): + yield from self._collectfile(path) + elif not direntry.is_dir(): # Broken symlink or invalid/missing file. continue elif path.join("__init__.py").check(file=1): pkg_prefixes.add(path) -def _get_xunit_setup_teardown(holder, attr_name, param_obj=None): - """ - Return a callable to perform xunit-style setup or teardown if - the function exists in the ``holder`` object. - The ``param_obj`` parameter is the parameter which will be passed to the function - when the callable is called without arguments, defaults to the ``holder`` object. - Return ``None`` if a suitable callable is not found. - """ - # TODO: only needed because of Package! - param_obj = param_obj if param_obj is not None else holder - result = _get_non_fixture_func(holder, attr_name) - if result is not None: - arg_count = result.__code__.co_argcount - if inspect.ismethod(result): - arg_count -= 1 - if arg_count: - return lambda: result(param_obj) - else: - return result - - -def _call_with_optional_argument(func, arg): +def _call_with_optional_argument(func, arg) -> None: """Call the given function with the given argument if func accepts one argument, otherwise - calls func without arguments""" + calls func without arguments.""" arg_count = func.__code__.co_argcount if inspect.ismethod(func): arg_count -= 1 @@ -699,23 +734,28 @@ def _call_with_optional_argument(func, arg): func() -def _get_non_fixture_func(obj, name): +def _get_first_non_fixture_func(obj: object, names: Iterable[str]): """Return the attribute from the given object to be used as a setup/teardown - xunit-style function, but only if not marked as a fixture to - avoid calling it twice. - """ - meth = getattr(obj, name, None) - if fixtures.getfixturemarker(meth) is None: - return meth + xunit-style function, but only if not marked as a fixture to avoid calling it twice.""" + for name in names: + meth = getattr(obj, name, None) + if meth is not None and fixtures.getfixturemarker(meth) is None: + return meth class Class(PyCollector): - """ Collector for test methods. """ + """Collector for test methods.""" + + @classmethod + def from_parent(cls, parent, *, name, obj=None): + """The public constructor.""" + return super().from_parent(name=name, parent=parent) - def collect(self): + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: if not safe_getattr(self.obj, "__test__", True): return [] if hasinit(self.obj): + assert self.parent is not None self.warn( PytestCollectionWarning( "cannot collect test class %r because it has a " @@ -725,6 +765,7 @@ class Class(PyCollector): ) return [] elif hasnew(self.obj): + assert self.parent is not None self.warn( PytestCollectionWarning( "cannot collect test class %r because it has a " @@ -737,22 +778,22 @@ class Class(PyCollector): self._inject_setup_class_fixture() self._inject_setup_method_fixture() - return [Instance(name="()", parent=self)] + return [Instance.from_parent(self, name="()")] - def _inject_setup_class_fixture(self): - """Injects a hidden autouse, class scoped fixture into the collected class object + def _inject_setup_class_fixture(self) -> None: + """Inject a hidden autouse, class scoped fixture into the collected class object that invokes setup_class/teardown_class if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ - setup_class = _get_non_fixture_func(self.obj, "setup_class") + setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) teardown_class = getattr(self.obj, "teardown_class", None) if setup_class is None and teardown_class is None: return @fixtures.fixture(autouse=True, scope="class") - def xunit_setup_class_fixture(cls): + def xunit_setup_class_fixture(cls) -> Generator[None, None, None]: if setup_class is not None: func = getimfunc(setup_class) _call_with_optional_argument(func, self.obj) @@ -763,20 +804,20 @@ class Class(PyCollector): self.obj.__pytest_setup_class = xunit_setup_class_fixture - def _inject_setup_method_fixture(self): - """Injects a hidden autouse, function scoped fixture into the collected class object + def _inject_setup_method_fixture(self) -> None: + """Inject a hidden autouse, function scoped fixture into the collected class object that invokes setup_method/teardown_method if either or both are available. Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with other fixtures (#517). """ - setup_method = _get_non_fixture_func(self.obj, "setup_method") + setup_method = _get_first_non_fixture_func(self.obj, ("setup_method",)) teardown_method = getattr(self.obj, "teardown_method", None) if setup_method is None and teardown_method is None: return @fixtures.fixture(autouse=True, scope="function") - def xunit_setup_method_fixture(self, request): + def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]: method = request.function if setup_method is not None: func = getattr(self, "setup_method") @@ -791,86 +832,52 @@ class Class(PyCollector): class Instance(PyCollector): _ALLOW_MARKERS = False # hack, destroy later - # instances share the object with their parents in a way + # Instances share the object with their parents in a way # that duplicates markers instances if not taken out - # can be removed at node structure reorganization time + # can be removed at node structure reorganization time. def _getobj(self): - return self.parent.obj() + # TODO: Improve the type of `parent` such that assert/ignore aren't needed. + assert self.parent is not None + obj = self.parent.obj # type: ignore[attr-defined] + return obj() - def collect(self): + def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: self.session._fixturemanager.parsefactories(self) - return super(Instance, self).collect() + return super().collect() def newinstance(self): self.obj = self._getobj() return self.obj -class FunctionMixin(PyobjMixin): - """ mixin for the code common to Function and Generator. - """ - - def setup(self): - """ perform setup for this test function. """ - if isinstance(self.parent, Instance): - self.parent.newinstance() - self.obj = self._getobj() - - def _prunetraceback(self, excinfo): - if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): - code = _pytest._code.Code(get_real_func(self.obj)) - path, firstlineno = code.path, code.firstlineno - traceback = excinfo.traceback - ntraceback = traceback.cut(path=path, firstlineno=firstlineno) - if ntraceback == traceback: - ntraceback = ntraceback.cut(path=path) - if ntraceback == traceback: - ntraceback = ntraceback.filter(filter_traceback) - if not ntraceback: - ntraceback = traceback - - excinfo.traceback = ntraceback.filter() - # issue364: mark all but first and last frames to - # only show a single-line message for each frame - if self.config.getoption("tbstyle", "auto") == "auto": - if len(excinfo.traceback) > 2: - for entry in excinfo.traceback[1:-1]: - entry.set_repr_style("short") - - def repr_failure(self, excinfo, outerr=None): - assert outerr is None, "XXX outerr usage is deprecated" - style = self.config.getoption("tbstyle", "auto") - if style == "auto": - style = "long" - return self._repr_failure_py(excinfo, style=style) - - -def hasinit(obj): - init = getattr(obj, "__init__", None) +def hasinit(obj: object) -> bool: + init = getattr(obj, "__init__", None) # type: object if init: return init != object.__init__ + return False -def hasnew(obj): - new = getattr(obj, "__new__", None) +def hasnew(obj: object) -> bool: + new = getattr(obj, "__new__", None) # type: object if new: return new != object.__new__ + return False -class CallSpec2(object): - def __init__(self, metafunc): +@final +class CallSpec2: + def __init__(self, metafunc: "Metafunc") -> None: self.metafunc = metafunc - self.funcargs = {} - self._idlist = [] - self.params = {} - self._globalid = NOTSET - self._globalparam = NOTSET - self._arg2scopenum = {} # used for sorting parametrized resources - self.marks = [] - self.indices = {} - - def copy(self): + self.funcargs = {} # type: Dict[str, object] + self._idlist = [] # type: List[str] + self.params = {} # type: Dict[str, object] + # Used for sorting parametrized resources. + self._arg2scopenum = {} # type: Dict[str, int] + self.marks = [] # type: List[Mark] + self.indices = {} # type: Dict[str, int] + + def copy(self) -> "CallSpec2": cs = CallSpec2(self.metafunc) cs.funcargs.update(self.funcargs) cs.params.update(self.params) @@ -878,133 +885,168 @@ class CallSpec2(object): cs.indices.update(self.indices) cs._arg2scopenum.update(self._arg2scopenum) cs._idlist = list(self._idlist) - cs._globalid = self._globalid - cs._globalparam = self._globalparam return cs - def _checkargnotcontained(self, arg): + def _checkargnotcontained(self, arg: str) -> None: if arg in self.params or arg in self.funcargs: - raise ValueError("duplicate %r" % (arg,)) + raise ValueError("duplicate {!r}".format(arg)) - def getparam(self, name): + def getparam(self, name: str) -> object: try: return self.params[name] - except KeyError: - if self._globalparam is NOTSET: - raise ValueError(name) - return self._globalparam + except KeyError as e: + raise ValueError(name) from e @property - def id(self): - return "-".join(map(str, filter(None, self._idlist))) + def id(self) -> str: + return "-".join(map(str, self._idlist)) - def setmulti2(self, valtypes, argnames, valset, id, marks, scopenum, param_index): + def setmulti2( + self, + valtypes: Mapping[str, "Literal['params', 'funcargs']"], + argnames: typing.Sequence[str], + valset: Iterable[object], + id: str, + marks: Iterable[Union[Mark, MarkDecorator]], + scopenum: int, + param_index: int, + ) -> None: for arg, val in zip(argnames, valset): self._checkargnotcontained(arg) valtype_for_arg = valtypes[arg] - getattr(self, valtype_for_arg)[arg] = val + if valtype_for_arg == "params": + self.params[arg] = val + elif valtype_for_arg == "funcargs": + self.funcargs[arg] = val + else: # pragma: no cover + assert False, "Unhandled valtype for arg: {}".format(valtype_for_arg) self.indices[arg] = param_index self._arg2scopenum[arg] = scopenum self._idlist.append(id) self.marks.extend(normalize_mark_list(marks)) - def setall(self, funcargs, id, param): - for x in funcargs: - self._checkargnotcontained(x) - self.funcargs.update(funcargs) - if id is not NOTSET: - self._idlist.append(id) - if param is not NOTSET: - assert self._globalparam is NOTSET - self._globalparam = param - for arg in funcargs: - self._arg2scopenum[arg] = fixtures.scopenum_function +@final +class Metafunc: + """Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook. -class Metafunc(fixtures.FuncargnamesCompatAttr): - """ - Metafunc objects are passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook. They help to inspect a test function and to generate tests according to test configuration or values specified in the class or module where a test function is defined. """ - def __init__(self, definition, fixtureinfo, config, cls=None, module=None): - assert ( - isinstance(definition, FunctionDefinition) - or type(definition).__name__ == "DefinitionMock" - ) + def __init__( + self, + definition: "FunctionDefinition", + fixtureinfo: fixtures.FuncFixtureInfo, + config: Config, + cls=None, + module=None, + ) -> None: self.definition = definition - #: access to the :class:`_pytest.config.Config` object for the test session + #: Access to the :class:`_pytest.config.Config` object for the test session. self.config = config - #: the module object where the test function is defined in. + #: The module object where the test function is defined in. self.module = module - #: underlying python test function + #: Underlying Python test function. self.function = definition.obj - #: set of fixture names required by the test function + #: Set of fixture names required by the test function. self.fixturenames = fixtureinfo.names_closure - #: class object where the test function is defined in or ``None``. + #: Class object where the test function is defined in or ``None``. self.cls = cls - self._calls = [] - self._ids = set() + self._calls = [] # type: List[CallSpec2] self._arg2fixturedefs = fixtureinfo.name2fixturedefs - def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None): - """ Add new invocations to the underlying test function using the list + def parametrize( + self, + argnames: Union[str, List[str], Tuple[str, ...]], + argvalues: Iterable[Union[ParameterSet, typing.Sequence[object], object]], + indirect: Union[bool, typing.Sequence[str]] = False, + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ] = None, + scope: "Optional[_Scope]" = None, + *, + _param_mark: Optional[Mark] = None + ) -> None: + """Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed during the collection phase. If you need to setup expensive resources see about setting indirect to do it rather at test setup time. - :arg argnames: a comma-separated string denoting one or more argument - names, or a list/tuple of argument strings. + :param argnames: + A comma-separated string denoting one or more argument names, or + a list/tuple of argument strings. + + :param argvalues: + The list of argvalues determines how often a test is invoked with + different argument values. - :arg argvalues: The list of argvalues determines how often a - test is invoked with different argument values. If only one - argname was specified argvalues is a list of values. If N - argnames were specified, argvalues must be a list of N-tuples, - where each tuple-element specifies a value for its respective - argname. + If only one argname was specified argvalues is a list of values. + If N argnames were specified, argvalues must be a list of + N-tuples, where each tuple-element specifies a value for its + respective argname. - :arg indirect: The list of argnames or boolean. A list of arguments' - names (subset of argnames). If True the list contains all names from - the argnames. Each argvalue corresponding to an argname in this list will + :param indirect: + A list of arguments' names (subset of argnames) or a boolean. + If True the list contains all names from the argnames. Each + argvalue corresponding to an argname in this list will be passed as request.param to its respective argname fixture function so that it can perform more expensive setups during the setup phase of a test rather than at collection time. - :arg ids: list of string ids, or a callable. - If strings, each is corresponding to the argvalues so that they are - part of the test id. If None is given as id of specific test, the - automatically generated id for that argument will be used. - If callable, it should take one argument (a single argvalue) and return - a string or return None. If None, the automatically generated id for that - argument will be used. + :param ids: + Sequence of (or generator for) ids for ``argvalues``, + or a callable to return part of the id for each argvalue. + + With sequences (and generators like ``itertools.count()``) the + returned ids should be of type ``string``, ``int``, ``float``, + ``bool``, or ``None``. + They are mapped to the corresponding index in ``argvalues``. + ``None`` means to use the auto-generated id. + + If it is a callable it will be called for each entry in + ``argvalues``, and the return value is used as part of the + auto-generated id for the whole set (where parts are joined with + dashes ("-")). + This is useful to provide more specific ids for certain items, e.g. + dates. Returning ``None`` will use an auto-generated id. + If no ids are provided they will be generated automatically from the argvalues. - :arg scope: if specified it denotes the scope of the parameters. + :param scope: + If specified it denotes the scope of the parameters. The scope is used for grouping tests by parameter instances. It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ from _pytest.fixtures import scope2index - from _pytest.mark import ParameterSet argnames, parameters = ParameterSet._for_parametrize( argnames, argvalues, self.function, self.config, - function_definition=self.definition, + nodeid=self.definition.nodeid, ) del argvalues + if "request" in argnames: + fail( + "'request' is a reserved name and cannot be used in @pytest.mark.parametrize", + pytrace=False, + ) + if scope is None: scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) @@ -1012,15 +1054,27 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): arg_values_types = self._resolve_arg_value_types(argnames, indirect) - ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition) + # Use any already (possibly) generated ids with parametrize Marks. + if _param_mark and _param_mark._param_ids_from: + generated_ids = _param_mark._param_ids_from._param_ids_generated + if generated_ids is not None: + ids = generated_ids + + ids = self._resolve_arg_ids( + argnames, ids, parameters, nodeid=self.definition.nodeid + ) + + # Store used (possibly generated) ids with parametrize Marks. + if _param_mark and _param_mark._param_ids_from and generated_ids is None: + object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids) scopenum = scope2index( scope, descr="parametrize() call in {}".format(self.function.__name__) ) - # create the new calls: if we are parametrize() multiple times (by applying the decorator + # Create the new calls: if we are parametrize() multiple times (by applying the decorator # more than once) then we accumulate those calls generating the cartesian product - # of all calls + # of all calls. newcalls = [] for callspec in self._calls or [CallSpec2(self)]: for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)): @@ -1037,55 +1091,97 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): newcalls.append(newcallspec) self._calls = newcalls - def _resolve_arg_ids(self, argnames, ids, parameters, item): - """Resolves the actual ids for the given argnames, based on the ``ids`` parameter given + def _resolve_arg_ids( + self, + argnames: typing.Sequence[str], + ids: Optional[ + Union[ + Iterable[Union[None, str, float, int, bool]], + Callable[[Any], Optional[object]], + ] + ], + parameters: typing.Sequence[ParameterSet], + nodeid: str, + ) -> List[str]: + """Resolve the actual ids for the given argnames, based on the ``ids`` parameter given to ``parametrize``. - :param List[str] argnames: list of argument names passed to ``parametrize()``. - :param ids: the ids parameter of the parametrized call (see docs). - :param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``. - :param Item item: the item that generated this parametrized call. + :param List[str] argnames: List of argument names passed to ``parametrize()``. + :param ids: The ids parameter of the parametrized call (see docs). + :param List[ParameterSet] parameters: The list of parameter values, same size as ``argnames``. + :param str str: The nodeid of the item that generated this parametrized call. :rtype: List[str] - :return: the list of ids for each argname given + :returns: The list of ids for each argname given. """ - from _pytest._io.saferepr import saferepr - - idfn = None - if callable(ids): + if ids is None: + idfn = None + ids_ = None + elif callable(ids): idfn = ids - ids = None - if ids: - func_name = self.function.__name__ - if len(ids) != len(parameters): - msg = "In {}: {} parameter sets specified, with different number of ids: {}" - fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False) - for id_value in ids: - if id_value is not None and not isinstance(id_value, six.string_types): - msg = "In {}: ids must be list of strings, found: {} (type: {!r})" - fail( - msg.format(func_name, saferepr(id_value), type(id_value)), - pytrace=False, - ) - ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item) - return ids + ids_ = None + else: + idfn = None + ids_ = self._validate_ids(ids, parameters, self.function.__name__) + return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid) - def _resolve_arg_value_types(self, argnames, indirect): - """Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg" - to the function, based on the ``indirect`` parameter of the parametrized() call. + def _validate_ids( + self, + ids: Iterable[Union[None, str, float, int, bool]], + parameters: typing.Sequence[ParameterSet], + func_name: str, + ) -> List[Union[None, str]]: + try: + num_ids = len(ids) # type: ignore[arg-type] + except TypeError: + try: + iter(ids) + except TypeError as e: + raise TypeError("ids must be a callable or an iterable") from e + num_ids = len(parameters) + + # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849 + if num_ids != len(parameters) and num_ids != 0: + msg = "In {}: {} parameter sets specified, with different number of ids: {}" + fail(msg.format(func_name, len(parameters), num_ids), pytrace=False) + + new_ids = [] + for idx, id_value in enumerate(itertools.islice(ids, num_ids)): + if id_value is None or isinstance(id_value, str): + new_ids.append(id_value) + elif isinstance(id_value, (float, int, bool)): + new_ids.append(str(id_value)) + else: + msg = ( # type: ignore[unreachable] + "In {}: ids must be list of string/float/int/bool, " + "found: {} (type: {!r}) at index {}" + ) + fail( + msg.format(func_name, saferepr(id_value), type(id_value), idx), + pytrace=False, + ) + return new_ids - :param List[str] argnames: list of argument names passed to ``parametrize()``. - :param indirect: same ``indirect`` parameter of ``parametrize()``. + def _resolve_arg_value_types( + self, + argnames: typing.Sequence[str], + indirect: Union[bool, typing.Sequence[str]], + ) -> Dict[str, "Literal['params', 'funcargs']"]: + """Resolve if each parametrized argument must be considered a + parameter to a fixture or a "funcarg" to the function, based on the + ``indirect`` parameter of the parametrized() call. + + :param List[str] argnames: List of argument names passed to ``parametrize()``. + :param indirect: Same as the ``indirect`` parameter of ``parametrize()``. :rtype: Dict[str, str] A dict mapping each arg name to either: * "params" if the argname should be the parameter of a fixture of the same name. * "funcargs" if the argname should be a parameter to the parametrized test function. """ - valtypes = {} - if indirect is True: - valtypes = dict.fromkeys(argnames, "params") - elif indirect is False: - valtypes = dict.fromkeys(argnames, "funcargs") - elif isinstance(indirect, (tuple, list)): + if isinstance(indirect, bool): + valtypes = dict.fromkeys( + argnames, "params" if indirect else "funcargs" + ) # type: Dict[str, Literal["params", "funcargs"]] + elif isinstance(indirect, Sequence): valtypes = dict.fromkeys(argnames, "funcargs") for arg in indirect: if arg not in argnames: @@ -1096,15 +1192,25 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): pytrace=False, ) valtypes[arg] = "params" + else: + fail( + "In {func}: expected Sequence or boolean for indirect, got {type}".format( + type=type(indirect).__name__, func=self.function.__name__ + ), + pytrace=False, + ) return valtypes - def _validate_if_using_arg_names(self, argnames, indirect): - """ - Check if all argnames are being used, by default values, or directly/indirectly. - - :param List[str] argnames: list of argument names passed to ``parametrize()``. - :param indirect: same ``indirect`` parameter of ``parametrize()``. - :raise ValueError: if validation fails. + def _validate_if_using_arg_names( + self, + argnames: typing.Sequence[str], + indirect: Union[bool, typing.Sequence[str]], + ) -> None: + """Check if all argnames are being used, by default values, or directly/indirectly. + + :param List[str] argnames: List of argument names passed to ``parametrize()``. + :param indirect: Same as the ``indirect`` parameter of ``parametrize()``. + :raises ValueError: If validation fails. """ default_arg_names = set(get_default_arg_names(self.function)) func_name = self.function.__name__ @@ -1118,7 +1224,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): pytrace=False, ) else: - if isinstance(indirect, (tuple, list)): + if isinstance(indirect, Sequence): name = "fixture" if arg in indirect else "argument" else: name = "fixture" if indirect else "argument" @@ -1128,7 +1234,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): ) -def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): +def _find_parametrized_scope( + argnames: typing.Sequence[str], + arg2fixturedefs: Mapping[str, typing.Sequence[fixtures.FixtureDef[object]]], + indirect: Union[bool, typing.Sequence[str]], +) -> "fixtures._Scope": """Find the most appropriate scope for a parametrized call based on its arguments. When there's at least one direct argument, always use "function" scope. @@ -1138,9 +1248,7 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): Related to issue #1832, based on code posted by @Kingdread. """ - from _pytest.fixtures import scopes - - if isinstance(indirect, (list, tuple)): + if isinstance(indirect, Sequence): all_arguments_are_fixtures = len(indirect) == len(argnames) else: all_arguments_are_fixtures = bool(indirect) @@ -1153,41 +1261,49 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): if name in argnames ] if used_scopes: - # Takes the most narrow scope from used fixtures - for scope in reversed(scopes): + # Takes the most narrow scope from used fixtures. + for scope in reversed(fixtures.scopes): if scope in used_scopes: return scope return "function" -def _ascii_escaped_by_config(val, config): +def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str: if config is None: escape_option = False else: escape_option = config.getini( "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" ) - return val if escape_option else ascii_escaped(val) - - -def _idval(val, argname, idx, idfn, item, config): + # TODO: If escaping is turned off and the user passes bytes, + # will return a bytes. For now we ignore this but the + # code *probably* doesn't handle this case. + return val if escape_option else ascii_escaped(val) # type: ignore + + +def _idval( + val: object, + argname: str, + idx: int, + idfn: Optional[Callable[[Any], Optional[object]]], + nodeid: Optional[str], + config: Optional[Config], +) -> str: if idfn: try: generated_id = idfn(val) if generated_id is not None: val = generated_id except Exception as e: - # See issue https://github.com/pytest-dev/pytest/issues/2169 - msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n" - msg = msg.format(item.nodeid, argname, idx) - # we only append the exception type and message because on Python 2 reraise does nothing - msg += " {}: {}\n".format(type(e).__name__, e) - six.raise_from(ValueError(msg), e) + prefix = "{}: ".format(nodeid) if nodeid is not None else "" + msg = "error raised while trying to determine id of parameter '{}' at position {}" + msg = prefix + msg.format(argname, idx) + raise ValueError(msg) from e elif config: hook_id = config.hook.pytest_make_parametrize_id( config=config, val=val, argname=argname - ) + ) # type: Optional[str] if hook_id: return hook_id @@ -1197,40 +1313,72 @@ def _idval(val, argname, idx, idfn, item, config): return str(val) elif isinstance(val, REGEX_TYPE): return ascii_escaped(val.pattern) - elif enum is not None and isinstance(val, enum.Enum): + elif val is NOTSET: + # Fallback to default. Note that NOTSET is an enum.Enum. + pass + elif isinstance(val, enum.Enum): return str(val) - elif (isclass(val) or isfunction(val)) and hasattr(val, "__name__"): - return val.__name__ + elif isinstance(getattr(val, "__name__", None), str): + # Name of a class, function, module, etc. + name = getattr(val, "__name__") # type: str + return name return str(argname) + str(idx) -def _idvalset(idx, parameterset, argnames, idfn, ids, item, config): +def _idvalset( + idx: int, + parameterset: ParameterSet, + argnames: Iterable[str], + idfn: Optional[Callable[[Any], Optional[object]]], + ids: Optional[List[Union[None, str]]], + nodeid: Optional[str], + config: Optional[Config], +) -> str: if parameterset.id is not None: return parameterset.id - if ids is None or (idx >= len(ids) or ids[idx] is None): + id = None if ids is None or idx >= len(ids) else ids[idx] + if id is None: this_id = [ - _idval(val, argname, idx, idfn, item=item, config=config) + _idval(val, argname, idx, idfn, nodeid=nodeid, config=config) for val, argname in zip(parameterset.values, argnames) ] return "-".join(this_id) else: - return _ascii_escaped_by_config(ids[idx], config) - - -def idmaker(argnames, parametersets, idfn=None, ids=None, config=None, item=None): - ids = [ - _idvalset(valindex, parameterset, argnames, idfn, ids, config=config, item=item) + return _ascii_escaped_by_config(id, config) + + +def idmaker( + argnames: Iterable[str], + parametersets: Iterable[ParameterSet], + idfn: Optional[Callable[[Any], Optional[object]]] = None, + ids: Optional[List[Union[None, str]]] = None, + config: Optional[Config] = None, + nodeid: Optional[str] = None, +) -> List[str]: + resolved_ids = [ + _idvalset( + valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid + ) for valindex, parameterset in enumerate(parametersets) ] - if len(set(ids)) != len(ids): - # The ids are not unique - duplicates = [testid for testid in ids if ids.count(testid) > 1] - counters = collections.defaultdict(lambda: 0) - for index, testid in enumerate(ids): - if testid in duplicates: - ids[index] = testid + str(counters[testid]) - counters[testid] += 1 - return ids + + # All IDs must be unique! + unique_ids = set(resolved_ids) + if len(unique_ids) != len(resolved_ids): + + # Record the number of occurrences of each test ID. + test_id_counts = Counter(resolved_ids) + + # Map the test ID to its next suffix. + test_id_suffixes = defaultdict(int) # type: Dict[str, int] + + # Suffix non-unique IDs to make them unique. + for index, test_id in enumerate(resolved_ids): + if test_id_counts[test_id] > 1: + resolved_ids[index] = "{}{}".format(test_id, test_id_suffixes[test_id]) + test_id_suffixes[test_id] += 1 + + return resolved_ids def show_fixtures_per_test(config): @@ -1239,7 +1387,7 @@ def show_fixtures_per_test(config): return wrap_session(config, _show_fixtures_per_test) -def _show_fixtures_per_test(config, session): +def _show_fixtures_per_test(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1248,10 +1396,10 @@ def _show_fixtures_per_test(config, session): verbose = config.getvalue("verbose") def get_best_relpath(func): - loc = getlocation(func, curdir) - return curdir.bestrelpath(loc) + loc = getlocation(func, str(curdir)) + return curdir.bestrelpath(py.path.local(loc)) - def write_fixture(fixture_def): + def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None: argname = fixture_def.argname if verbose <= 0 and argname.startswith("_"): return @@ -1261,43 +1409,41 @@ def _show_fixtures_per_test(config, session): else: funcargspec = argname tw.line(funcargspec, green=True) - fixture_doc = fixture_def.func.__doc__ + fixture_doc = inspect.getdoc(fixture_def.func) if fixture_doc: write_docstring(tw, fixture_doc) else: tw.line(" no docstring available", red=True) - def write_item(item): - try: - info = item._fixtureinfo - except AttributeError: - # doctests items have no _fixtureinfo attribute - return - if not info.name2fixturedefs: - # this test item does not use any fixtures + def write_item(item: nodes.Item) -> None: + # Not all items have _fixtureinfo attribute. + info = getattr(item, "_fixtureinfo", None) # type: Optional[FuncFixtureInfo] + if info is None or not info.name2fixturedefs: + # This test item does not use any fixtures. return tw.line() tw.sep("-", "fixtures used by {}".format(item.name)) - tw.sep("-", "({})".format(get_best_relpath(item.function))) - # dict key not used in loop but needed for sorting + # TODO: Fix this type ignore. + tw.sep("-", "({})".format(get_best_relpath(item.function))) # type: ignore[attr-defined] + # dict key not used in loop but needed for sorting. for _, fixturedefs in sorted(info.name2fixturedefs.items()): assert fixturedefs is not None if not fixturedefs: continue - # last item is expected to be the one used by the test item + # Last item is expected to be the one used by the test item. write_fixture(fixturedefs[-1]) for session_item in session.items: write_item(session_item) -def showfixtures(config): +def showfixtures(config: Config) -> Union[int, ExitCode]: from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) -def _showfixtures_main(config, session): +def _showfixtures_main(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1308,14 +1454,14 @@ def _showfixtures_main(config, session): fm = session._fixturemanager available = [] - seen = set() + seen = set() # type: Set[Tuple[str, str]] for argname, fixturedefs in fm._arg2fixturedefs.items(): assert fixturedefs is not None if not fixturedefs: continue for fixturedef in fixturedefs: - loc = getlocation(fixturedef.func, curdir) + loc = getlocation(fixturedef.func, str(curdir)) if (fixturedef.argname, loc) in seen: continue seen.add((fixturedef.argname, loc)) @@ -1323,7 +1469,7 @@ def _showfixtures_main(config, session): ( len(fixturedef.baseid), fixturedef.func.__module__, - curdir.bestrelpath(loc), + curdir.bestrelpath(py.path.local(loc)), fixturedef.argname, fixturedef, ) @@ -1335,7 +1481,7 @@ def _showfixtures_main(config, session): if currentmodule != module: if not module.startswith("_pytest."): tw.line() - tw.sep("-", "fixtures defined from %s" % (module,)) + tw.sep("-", "fixtures defined from {}".format(module)) currentmodule = module if verbose <= 0 and argname[0] == "_": continue @@ -1345,56 +1491,80 @@ def _showfixtures_main(config, session): if verbose > 0: tw.write(" -- %s" % bestrel, yellow=True) tw.write("\n") - loc = getlocation(fixturedef.func, curdir) - doc = fixturedef.func.__doc__ or "" + loc = getlocation(fixturedef.func, str(curdir)) + doc = inspect.getdoc(fixturedef.func) if doc: write_docstring(tw, doc) else: - tw.line(" %s: no docstring available" % (loc,), red=True) + tw.line(" {}: no docstring available".format(loc), red=True) tw.line() -def write_docstring(tw, doc, indent=" "): - doc = doc.rstrip() - if "\n" in doc: - firstline, rest = doc.split("\n", 1) - else: - firstline, rest = doc, "" - - if firstline.strip(): - tw.line(indent + firstline.strip()) - - if rest: - for line in dedent(rest).split("\n"): - tw.write(indent + line + "\n") - - -class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): - """ a Function Item is responsible for setting up and executing a - Python test function. +def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None: + for line in doc.split("\n"): + tw.line(indent + line) + + +class Function(PyobjMixin, nodes.Item): + """An Item responsible for setting up and executing a Python test function. + + param name: + The full function name, including any decorations like those + added by parametrization (``my_func[my_param]``). + param parent: + The parent Node. + param config: + The pytest Config object. + param callspec: + If given, this is function has been parametrized and the callspec contains + meta information about the parametrization. + param callobj: + If given, the object which will be called when the Function is invoked, + otherwise the callobj will be obtained from ``parent`` using ``originalname``. + param keywords: + Keywords bound to the function object for "-k" matching. + param session: + The pytest Session object. + param fixtureinfo: + Fixture information already resolved at this fixture node.. + param originalname: + The attribute name to use for accessing the underlying function object. + Defaults to ``name``. Set this if name is different from the original name, + for example when it contains decorations like those added by parametrization + (``my_func[my_param]``). """ - # disable since functions handle it themselves + # Disable since functions handle it themselves. _ALLOW_MARKERS = False def __init__( self, - name, + name: str, parent, - args=None, - config=None, - callspec=None, + config: Optional[Config] = None, + callspec: Optional[CallSpec2] = None, callobj=NOTSET, keywords=None, - session=None, - fixtureinfo=None, - originalname=None, - ): - super(Function, self).__init__(name, parent, config=config, session=session) - self._args = args + session: Optional[Session] = None, + fixtureinfo: Optional[FuncFixtureInfo] = None, + originalname: Optional[str] = None, + ) -> None: + super().__init__(name, parent, config=config, session=session) + if callobj is not NOTSET: self.obj = callobj + #: Original function name, without any decorations (for example + #: parametrization adds a ``"[...]"`` suffix to function names), used to access + #: the underlying function object from ``parent`` (in case ``callobj`` is not given + #: explicitly). + #: + #: .. versionadded:: 3.0 + self.originalname = originalname or name + + # Note: when FunctionDefinition is introduced, we should change ``originalname`` + # to a readonly property that returns FunctionDefinition.name. + self.keywords.update(self.obj.__dict__) self.own_markers.extend(get_unpacked_marks(self.obj)) if callspec: @@ -1414,67 +1584,90 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): # https://github.com/pytest-dev/pytest/issues/4569 self.keywords.update( - dict.fromkeys( - [ - mark.name - for mark in self.iter_markers() - if mark.name not in self.keywords - ], - True, - ) + { + mark.name: True + for mark in self.iter_markers() + if mark.name not in self.keywords + } ) if fixtureinfo is None: fixtureinfo = self.session._fixturemanager.getfixtureinfo( self, self.obj, self.cls, funcargs=True ) - self._fixtureinfo = fixtureinfo + self._fixtureinfo = fixtureinfo # type: FuncFixtureInfo self.fixturenames = fixtureinfo.names_closure self._initrequest() - #: original function name, without any decorations (for example - #: parametrization adds a ``"[...]"`` suffix to function names). - #: - #: .. versionadded:: 3.0 - self.originalname = originalname + @classmethod + def from_parent(cls, parent, **kw): # todo: determine sound type limitations + """The public constructor.""" + return super().from_parent(parent=parent, **kw) - def _initrequest(self): - self.funcargs = {} + def _initrequest(self) -> None: + self.funcargs = {} # type: Dict[str, object] self._request = fixtures.FixtureRequest(self) @property def function(self): - "underlying python 'function' object" + """Underlying python 'function' object.""" return getimfunc(self.obj) def _getobj(self): - name = self.name - i = name.find("[") # parametrization - if i != -1: - name = name[:i] - return getattr(self.parent.obj, name) + assert self.parent is not None + return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined] @property def _pyfuncitem(self): - "(compatonly) for code expecting pytest-2.2 style request objects" + """(compatonly) for code expecting pytest-2.2 style request objects.""" return self - def runtest(self): - """ execute the underlying test function. """ + def runtest(self) -> None: + """Execute the underlying test function.""" self.ihook.pytest_pyfunc_call(pyfuncitem=self) - def setup(self): - super(Function, self).setup() - fixtures.fillfixtures(self) + def setup(self) -> None: + if isinstance(self.parent, Instance): + self.parent.newinstance() + self.obj = self._getobj() + self._request._fillfixtures() + + def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: + if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): + code = _pytest._code.Code(get_real_func(self.obj)) + path, firstlineno = code.path, code.firstlineno + traceback = excinfo.traceback + ntraceback = traceback.cut(path=path, firstlineno=firstlineno) + if ntraceback == traceback: + ntraceback = ntraceback.cut(path=path) + if ntraceback == traceback: + ntraceback = ntraceback.filter(filter_traceback) + if not ntraceback: + ntraceback = traceback + + excinfo.traceback = ntraceback.filter() + # issue364: mark all but first and last frames to + # only show a single-line message for each frame. + if self.config.getoption("tbstyle", "auto") == "auto": + if len(excinfo.traceback) > 2: + for entry in excinfo.traceback[1:-1]: + entry.set_repr_style("short") + + # TODO: Type ignored -- breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, excinfo: ExceptionInfo[BaseException], + ) -> Union[str, TerminalRepr]: + style = self.config.getoption("tbstyle", "auto") + if style == "auto": + style = "long" + return self._repr_failure_py(excinfo, style=style) class FunctionDefinition(Function): - """ - internal hack until we get actual definition nodes instead of the - crappy metafunc hack - """ + """Internal hack until we get actual definition nodes instead of the + crappy metafunc hack.""" - def runtest(self): + def runtest(self) -> None: raise RuntimeError("function definitions are not supposed to be used") setup = runtest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py index f6e475c3a24..f5ad04a12c9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/python_api.py @@ -1,44 +1,33 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - import math import pprint -import sys -import warnings +from collections.abc import Iterable +from collections.abc import Mapping +from collections.abc import Sized from decimal import Decimal from numbers import Number - -from more_itertools.more import always_iterable -from six.moves import filterfalse -from six.moves import zip +from types import TracebackType +from typing import Any +from typing import Callable +from typing import cast +from typing import Generic +from typing import Optional +from typing import Pattern +from typing import Tuple +from typing import TypeVar +from typing import Union import _pytest._code -from _pytest import deprecated -from _pytest.compat import isclass -from _pytest.compat import Iterable -from _pytest.compat import Mapping -from _pytest.compat import Sized +from _pytest.compat import final +from _pytest.compat import overload from _pytest.compat import STRING_TYPES +from _pytest.compat import TYPE_CHECKING from _pytest.outcomes import fail -BASE_TYPE = (type, STRING_TYPES) - - -def _cmp_raises_type_error(self, other): - """__cmp__ implementation which raises TypeError. Used - by Approx base classes to implement only == and != and raise a - TypeError for other comparisons. - - Needed in Python 2 only, Python 3 all it takes is not implementing the - other operators at all. - """ - __tracebackhide__ = True - raise TypeError( - "Comparison operators other than == and != not supported by approx objects" - ) +if TYPE_CHECKING: + from typing import Type -def _non_numeric_type_error(value, at): +def _non_numeric_type_error(value, at: Optional[str]) -> TypeError: at_str = " at {}".format(at) if at else "" return TypeError( "cannot make approximate comparisons to non-numeric values: {!r} {}".format( @@ -50,17 +39,15 @@ def _non_numeric_type_error(value, at): # builtin pytest.approx helper -class ApproxBase(object): - """ - Provide shared utilities for making approximate comparisons between numbers - or sequences of numbers. - """ +class ApproxBase: + """Provide shared utilities for making approximate comparisons between + numbers or sequences of numbers.""" # Tell numpy to use our `__eq__` operator instead of its. __array_ufunc__ = None __array_priority__ = 100 - def __init__(self, expected, rel=None, abs=None, nan_ok=False): + def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: __tracebackhide__ = True self.expected = expected self.abs = abs @@ -68,36 +55,32 @@ class ApproxBase(object): self.nan_ok = nan_ok self._check_type() - def __repr__(self): + def __repr__(self) -> str: raise NotImplementedError - def __eq__(self, actual): + def __eq__(self, actual) -> bool: return all( a == self._approx_scalar(x) for a, x in self._yield_comparisons(actual) ) - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore - def __ne__(self, actual): + def __ne__(self, actual) -> bool: return not (actual == self) - if sys.version_info[0] == 2: - __cmp__ = _cmp_raises_type_error - - def _approx_scalar(self, x): + def _approx_scalar(self, x) -> "ApproxScalar": return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) def _yield_comparisons(self, actual): - """ - Yield all the pairs of numbers to be compared. This is used to - implement the `__eq__` method. + """Yield all the pairs of numbers to be compared. + + This is used to implement the `__eq__` method. """ raise NotImplementedError - def _check_type(self): - """ - Raise a TypeError if the expected value is not a valid type. - """ + def _check_type(self) -> None: + """Raise a TypeError if the expected value is not a valid type.""" # This is only a concern if the expected value is a sequence. In every # other case, the approx() function ensures that the expected value has # a numeric type. For this reason, the default is to do nothing. The @@ -114,27 +97,24 @@ def _recursive_list_map(f, x): class ApproxNumpy(ApproxBase): - """ - Perform approximate comparisons where the expected value is numpy array. - """ + """Perform approximate comparisons where the expected value is numpy array.""" - def __repr__(self): + def __repr__(self) -> str: list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist()) return "approx({!r})".format(list_scalars) - if sys.version_info[0] == 2: - __cmp__ = _cmp_raises_type_error - - def __eq__(self, actual): + def __eq__(self, actual) -> bool: import numpy as np - # self.expected is supposed to always be an array here + # self.expected is supposed to always be an array here. if not np.isscalar(actual): try: actual = np.asarray(actual) - except: # noqa - raise TypeError("cannot compare '{}' to numpy.ndarray".format(actual)) + except Exception as e: + raise TypeError( + "cannot compare '{}' to numpy.ndarray".format(actual) + ) from e if not np.isscalar(actual) and actual.shape != self.expected.shape: return False @@ -157,17 +137,15 @@ class ApproxNumpy(ApproxBase): class ApproxMapping(ApproxBase): - """ - Perform approximate comparisons where the expected value is a mapping with - numeric values (the keys can be anything). - """ + """Perform approximate comparisons where the expected value is a mapping + with numeric values (the keys can be anything).""" - def __repr__(self): + def __repr__(self) -> str: return "approx({!r})".format( {k: self._approx_scalar(v) for k, v in self.expected.items()} ) - def __eq__(self, actual): + def __eq__(self, actual) -> bool: if set(actual.keys()) != set(self.expected.keys()): return False @@ -177,7 +155,7 @@ class ApproxMapping(ApproxBase): for k in self.expected.keys(): yield actual[k], self.expected[k] - def _check_type(self): + def _check_type(self) -> None: __tracebackhide__ = True for key, value in self.expected.items(): if isinstance(value, type(self.expected)): @@ -188,12 +166,9 @@ class ApproxMapping(ApproxBase): class ApproxSequencelike(ApproxBase): - """ - Perform approximate comparisons where the expected value is a sequence of - numbers. - """ + """Perform approximate comparisons where the expected value is a sequence of numbers.""" - def __repr__(self): + def __repr__(self) -> str: seq_type = type(self.expected) if seq_type not in (tuple, list, set): seq_type = list @@ -201,7 +176,7 @@ class ApproxSequencelike(ApproxBase): seq_type(self._approx_scalar(x) for x in self.expected) ) - def __eq__(self, actual): + def __eq__(self, actual) -> bool: if len(actual) != len(self.expected): return False return ApproxBase.__eq__(self, actual) @@ -209,7 +184,7 @@ class ApproxSequencelike(ApproxBase): def _yield_comparisons(self, actual): return zip(actual, self.expected) - def _check_type(self): + def _check_type(self) -> None: __tracebackhide__ = True for index, x in enumerate(self.expected): if isinstance(x, type(self.expected)): @@ -222,45 +197,39 @@ class ApproxSequencelike(ApproxBase): class ApproxScalar(ApproxBase): - """ - Perform approximate comparisons where the expected value is a single number. - """ + """Perform approximate comparisons where the expected value is a single number.""" - DEFAULT_ABSOLUTE_TOLERANCE = 1e-12 - DEFAULT_RELATIVE_TOLERANCE = 1e-6 + # Using Real should be better than this Union, but not possible yet: + # https://github.com/python/typeshed/pull/3108 + DEFAULT_ABSOLUTE_TOLERANCE = 1e-12 # type: Union[float, Decimal] + DEFAULT_RELATIVE_TOLERANCE = 1e-6 # type: Union[float, Decimal] - def __repr__(self): - """ - Return a string communicating both the expected value and the tolerance - for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode - plus/minus symbol if this is python3 (it's too hard to get right for - python2). + def __repr__(self) -> str: + """Return a string communicating both the expected value and the + tolerance for the comparison being made. + + For example, ``1.0 ± 1e-6``, ``(3+4j) ± 5e-6 ∠ ±180°``. """ - if isinstance(self.expected, complex): - return str(self.expected) # Infinities aren't compared using tolerances, so don't show a - # tolerance. - if math.isinf(self.expected): + # tolerance. Need to call abs to handle complex numbers, e.g. (inf + 1j). + if math.isinf(abs(self.expected)): return str(self.expected) # If a sensible tolerance can't be calculated, self.tolerance will # raise a ValueError. In this case, display '???'. try: vetted_tolerance = "{:.1e}".format(self.tolerance) + if isinstance(self.expected, complex) and not math.isinf(self.tolerance): + vetted_tolerance += " ∠ ±180°" except ValueError: vetted_tolerance = "???" - if sys.version_info[0] == 2: - return "{} +- {}".format(self.expected, vetted_tolerance) - else: - return u"{} \u00b1 {}".format(self.expected, vetted_tolerance) + return "{} ± {}".format(self.expected, vetted_tolerance) - def __eq__(self, actual): - """ - Return true if the given value is equal to the expected value within - the pre-specified tolerance. - """ + def __eq__(self, actual) -> bool: + """Return whether the given value is equal to the expected value + within the pre-specified tolerance.""" if _is_numpy_array(actual): # Call ``__eq__()`` manually to prevent infinite-recursion with # numpy<1.13. See #3748. @@ -286,16 +255,18 @@ class ApproxScalar(ApproxBase): return False # Return true if the two numbers are within the tolerance. - return abs(self.expected - actual) <= self.tolerance + result = abs(self.expected - actual) <= self.tolerance # type: bool + return result - __hash__ = None + # Ignore type because of https://github.com/python/mypy/issues/4266. + __hash__ = None # type: ignore @property def tolerance(self): - """ - Return the tolerance for the comparison. This could be either an - absolute tolerance or a relative tolerance, depending on what the user - specified or which would be larger. + """Return the tolerance for the comparison. + + This could be either an absolute tolerance or a relative tolerance, + depending on what the user specified or which would be larger. """ def set_default(x, default): @@ -339,17 +310,14 @@ class ApproxScalar(ApproxBase): class ApproxDecimal(ApproxScalar): - """ - Perform approximate comparisons where the expected value is a decimal. - """ + """Perform approximate comparisons where the expected value is a Decimal.""" DEFAULT_ABSOLUTE_TOLERANCE = Decimal("1e-12") DEFAULT_RELATIVE_TOLERANCE = Decimal("1e-6") -def approx(expected, rel=None, abs=None, nan_ok=False): - """ - Assert that two numbers (or two sets of numbers) are equal to each other +def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: + """Assert that two numbers (or two sets of numbers) are equal to each other within some tolerance. Due to the `intricacies of floating-point arithmetic`__, numbers that we @@ -518,7 +486,7 @@ def approx(expected, rel=None, abs=None, nan_ok=False): __tracebackhide__ = True if isinstance(expected, Decimal): - cls = ApproxDecimal + cls = ApproxDecimal # type: Type[ApproxBase] elif isinstance(expected, Number): cls = ApproxScalar elif isinstance(expected, Mapping): @@ -528,7 +496,8 @@ def approx(expected, rel=None, abs=None, nan_ok=False): elif ( isinstance(expected, Iterable) and isinstance(expected, Sized) - and not isinstance(expected, STRING_TYPES) + # Type ignored because the error is wrong -- not unreachable. + and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] ): cls = ApproxSequencelike else: @@ -537,14 +506,14 @@ def approx(expected, rel=None, abs=None, nan_ok=False): return cls(expected, rel, abs, nan_ok) -def _is_numpy_array(obj): - """ - Return true if the given object is a numpy array. Make a special effort to - avoid importing numpy unless it's really necessary. +def _is_numpy_array(obj: object) -> bool: + """Return true if the given object is a numpy array. + + A special effort is made to avoid importing numpy unless it's really necessary. """ import sys - np = sys.modules.get("numpy") + np = sys.modules.get("numpy") # type: Any if np is not None: return isinstance(obj, np.ndarray) return False @@ -552,22 +521,49 @@ def _is_numpy_array(obj): # builtin pytest.raises helper +_E = TypeVar("_E", bound=BaseException) + + +@overload +def raises( + expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], + *, + match: "Optional[Union[str, Pattern[str]]]" = ... +) -> "RaisesContext[_E]": + ... + -def raises(expected_exception, *args, **kwargs): - r""" - Assert that a code block/function call raises ``expected_exception`` +@overload # noqa: F811 +def raises( # noqa: F811 + expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], + func: Callable[..., Any], + *args: Any, + **kwargs: Any +) -> _pytest._code.ExceptionInfo[_E]: + ... + + +def raises( # noqa: F811 + expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], + *args: Any, + **kwargs: Any +) -> Union["RaisesContext[_E]", _pytest._code.ExceptionInfo[_E]]: + r"""Assert that a code block/function call raises ``expected_exception`` or raise a failure exception otherwise. - :kwparam match: if specified, a string containing a regular expression, + :kwparam match: + If specified, a string containing a regular expression, or a regular expression object, that is tested against the string representation of the exception using ``re.search``. To match a literal string that may contain `special characters`__, the pattern can first be escaped with ``re.escape``. - __ https://docs.python.org/3/library/re.html#regular-expression-syntax + (This is only used when ``pytest.raises`` is used as a context manager, + and passed through to the function otherwise. + When using ``pytest.raises`` as a function, you can use: + ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.) - :kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message - if the exception is not raised. See :ref:`the deprecation docs <raises message deprecated>` for a workaround. + __ https://docs.python.org/3/library/re.html#regular-expression-syntax .. currentmodule:: _pytest._code @@ -597,14 +593,6 @@ def raises(expected_exception, *args, **kwargs): >>> assert exc_info.type is ValueError >>> assert exc_info.value.args[0] == "value must be 42" - .. deprecated:: 4.1 - - In the context manager form you may use the keyword argument - ``message`` to specify a custom failure message that will be displayed - in case the ``pytest.raises`` check fails. This has been deprecated as it - is considered error prone as users often mean to use ``match`` instead. - See :ref:`the deprecation docs <raises message deprecated>` for a workaround. - .. note:: When using ``pytest.raises`` as a context manager, it's worthwhile to @@ -665,79 +653,87 @@ def raises(expected_exception, *args, **kwargs): the exception --> current frame stack --> local variables --> ``ExceptionInfo``) which makes Python keep all objects referenced from that cycle (including all local variables in the current - frame) alive until the next cyclic garbage collection run. See the - official Python ``try`` statement documentation for more detailed - information. - + frame) alive until the next cyclic garbage collection run. + More detailed information can be found in the official Python + documentation for :ref:`the try statement <python:try>`. """ __tracebackhide__ = True - for exc in filterfalse(isclass, always_iterable(expected_exception, BASE_TYPE)): - msg = ( - "exceptions must be old-style classes or" - " derived from BaseException, not %s" - ) - raise TypeError(msg % type(exc)) + + if isinstance(expected_exception, type): + excepted_exceptions = (expected_exception,) # type: Tuple[Type[_E], ...] + else: + excepted_exceptions = expected_exception + for exc in excepted_exceptions: + if not isinstance(exc, type) or not issubclass(exc, BaseException): # type: ignore[unreachable] + msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] + not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ + raise TypeError(msg.format(not_a)) message = "DID NOT RAISE {}".format(expected_exception) - match_expr = None if not args: - if "message" in kwargs: - message = kwargs.pop("message") - warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2) - if "match" in kwargs: - match_expr = kwargs.pop("match") + match = kwargs.pop("match", None) # type: Optional[Union[str, Pattern[str]]] if kwargs: msg = "Unexpected keyword arguments passed to pytest.raises: " msg += ", ".join(sorted(kwargs)) + msg += "\nUse context-manager form instead?" raise TypeError(msg) - return RaisesContext(expected_exception, message, match_expr) - elif isinstance(args[0], str): - warnings.warn(deprecated.RAISES_EXEC, stacklevel=2) - (code,) = args - assert isinstance(code, str) - frame = sys._getframe(1) - loc = frame.f_locals.copy() - loc.update(kwargs) - # print "raises frame scope: %r" % frame.f_locals - try: - code = _pytest._code.Source(code).compile(_genframe=frame) - exec(code, frame.f_globals, loc) - # XXX didn't mean f_globals == f_locals something special? - # this is destroyed here ... - except expected_exception: - return _pytest._code.ExceptionInfo.from_current() + return RaisesContext(expected_exception, message, match) else: func = args[0] + if not callable(func): + raise TypeError( + "{!r} object (type: {}) must be callable".format(func, type(func)) + ) try: func(*args[1:], **kwargs) - except expected_exception: - return _pytest._code.ExceptionInfo.from_current() + except expected_exception as e: + # We just caught the exception - there is a traceback. + assert e.__traceback__ is not None + return _pytest._code.ExceptionInfo.from_exc_info( + (type(e), e, e.__traceback__) + ) fail(message) -raises.Exception = fail.Exception +# This doesn't work with mypy for now. Use fail.Exception instead. +raises.Exception = fail.Exception # type: ignore -class RaisesContext(object): - def __init__(self, expected_exception, message, match_expr): +@final +class RaisesContext(Generic[_E]): + def __init__( + self, + expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]], + message: str, + match_expr: Optional[Union[str, "Pattern[str]"]] = None, + ) -> None: self.expected_exception = expected_exception self.message = message self.match_expr = match_expr - self.excinfo = None + self.excinfo = None # type: Optional[_pytest._code.ExceptionInfo[_E]] - def __enter__(self): + def __enter__(self) -> _pytest._code.ExceptionInfo[_E]: self.excinfo = _pytest._code.ExceptionInfo.for_later() return self.excinfo - def __exit__(self, *tp): + def __exit__( + self, + exc_type: Optional["Type[BaseException]"], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> bool: __tracebackhide__ = True - if tp[0] is None: + if exc_type is None: fail(self.message) - self.excinfo.__init__(tp) - suppress_exception = issubclass(self.excinfo.type, self.expected_exception) - if sys.version_info[0] == 2 and suppress_exception: - sys.exc_clear() - if self.match_expr is not None and suppress_exception: + assert self.excinfo is not None + if not issubclass(exc_type, self.expected_exception): + return False + # Cast to narrow the exception type now that it's verified. + exc_info = cast( + Tuple["Type[_E]", _E, TracebackType], (exc_type, exc_val, exc_tb) + ) + self.excinfo.fill_unfilled(exc_info) + if self.match_expr is not None: self.excinfo.match(self.match_expr) - return suppress_exception + return True diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py index 7abf2e93550..39d6de91455 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/recwarn.py @@ -1,25 +1,33 @@ -# -*- coding: utf-8 -*- -""" recording warnings during test function execution. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import inspect +"""Record warnings during test function execution.""" import re -import sys import warnings +from types import TracebackType +from typing import Any +from typing import Callable +from typing import Generator +from typing import Iterator +from typing import List +from typing import Optional +from typing import Pattern +from typing import Tuple +from typing import TypeVar +from typing import Union + +from _pytest.compat import final +from _pytest.compat import overload +from _pytest.compat import TYPE_CHECKING +from _pytest.fixtures import fixture +from _pytest.outcomes import fail -import six +if TYPE_CHECKING: + from typing import Type -import _pytest._code -from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS -from _pytest.deprecated import WARNS_EXEC -from _pytest.fixtures import yield_fixture -from _pytest.outcomes import fail +T = TypeVar("T") -@yield_fixture -def recwarn(): + +@fixture +def recwarn() -> Generator["WarningsRecorder", None, None]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. See http://docs.python.org/library/warnings.html for information @@ -31,9 +39,26 @@ def recwarn(): yield wrec -def deprecated_call(func=None, *args, **kwargs): - """context manager that can be used to ensure a block of code triggers a - ``DeprecationWarning`` or ``PendingDeprecationWarning``:: +@overload +def deprecated_call( + *, match: Optional[Union[str, "Pattern[str]"]] = ... +) -> "WarningsRecorder": + ... + + +@overload # noqa: F811 +def deprecated_call( # noqa: F811 + func: Callable[..., T], *args: Any, **kwargs: Any +) -> T: + ... + + +def deprecated_call( # noqa: F811 + func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any +) -> Union["WarningsRecorder", Any]: + """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning``. + + This function can be used as a context manager:: >>> import warnings >>> def api_call_v2(): @@ -43,9 +68,15 @@ def deprecated_call(func=None, *args, **kwargs): >>> with deprecated_call(): ... assert api_call_v2() == 200 - ``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``, - in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings - types above. + It can also be used by passing a function and ``*args`` and ``**kwargs``, + in which case it will ensure calling ``func(*args, **kwargs)`` produces one of + the warnings types above. The return value is the return value of the function. + + In the context manager form you may use the keyword argument ``match`` to assert + that the warning matches a text or regex. + + The context manager produces a list of :class:`warnings.WarningMessage` objects, + one for each warning raised. """ __tracebackhide__ = True if func is not None: @@ -53,7 +84,31 @@ def deprecated_call(func=None, *args, **kwargs): return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs) -def warns(expected_warning, *args, **kwargs): +@overload +def warns( + expected_warning: Optional[Union["Type[Warning]", Tuple["Type[Warning]", ...]]], + *, + match: "Optional[Union[str, Pattern[str]]]" = ... +) -> "WarningsChecker": + ... + + +@overload # noqa: F811 +def warns( # noqa: F811 + expected_warning: Optional[Union["Type[Warning]", Tuple["Type[Warning]", ...]]], + func: Callable[..., T], + *args: Any, + **kwargs: Any +) -> T: + ... + + +def warns( # noqa: F811 + expected_warning: Optional[Union["Type[Warning]", Tuple["Type[Warning]", ...]]], + *args: Any, + match: Optional[Union[str, "Pattern[str]"]] = None, + **kwargs: Any +) -> Union["WarningsChecker", Any]: r"""Assert that code raises a particular class of warning. Specifically, the parameter ``expected_warning`` can be a warning class or @@ -70,7 +125,7 @@ def warns(expected_warning, *args, **kwargs): ... warnings.warn("my warning", RuntimeWarning) In the context manager form you may use the keyword argument ``match`` to assert - that the exception matches a text or regex:: + that the warning matches a text or regex:: >>> with warns(UserWarning, match='must be 0 or None'): ... warnings.warn("value must be 0 or None", UserWarning) @@ -87,25 +142,18 @@ def warns(expected_warning, *args, **kwargs): """ __tracebackhide__ = True if not args: - match_expr = kwargs.pop("match", None) if kwargs: - warnings.warn( - PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2 - ) - return WarningsChecker(expected_warning, match_expr=match_expr) - elif isinstance(args[0], str): - warnings.warn(WARNS_EXEC, stacklevel=2) - (code,) = args - assert isinstance(code, str) - frame = sys._getframe(1) - loc = frame.f_locals.copy() - loc.update(kwargs) - - with WarningsChecker(expected_warning): - code = _pytest._code.Source(code).compile() - exec(code, frame.f_globals, loc) + msg = "Unexpected keyword arguments passed to pytest.warns: " + msg += ", ".join(sorted(kwargs)) + msg += "\nUse context-manager form instead?" + raise TypeError(msg) + return WarningsChecker(expected_warning, match_expr=match) else: func = args[0] + if not callable(func): + raise TypeError( + "{!r} object (type: {}) must be callable".format(func, type(func)) + ) with WarningsChecker(expected_warning): return func(*args[1:], **kwargs) @@ -116,29 +164,30 @@ class WarningsRecorder(warnings.catch_warnings): Adapted from `warnings.catch_warnings`. """ - def __init__(self): - super(WarningsRecorder, self).__init__(record=True) + def __init__(self) -> None: + # Type ignored due to the way typeshed handles warnings.catch_warnings. + super().__init__(record=True) # type: ignore[call-arg] self._entered = False - self._list = [] + self._list = [] # type: List[warnings.WarningMessage] @property - def list(self): + def list(self) -> List["warnings.WarningMessage"]: """The list of recorded warnings.""" return self._list - def __getitem__(self, i): + def __getitem__(self, i: int) -> "warnings.WarningMessage": """Get a recorded warning by index.""" return self._list[i] - def __iter__(self): + def __iter__(self) -> Iterator["warnings.WarningMessage"]: """Iterate through the recorded warnings.""" return iter(self._list) - def __len__(self): + def __len__(self) -> int: """The number of recorded warnings.""" return len(self._list) - def pop(self, cls=Warning): + def pop(self, cls: "Type[Warning]" = Warning) -> "warnings.WarningMessage": """Pop the first recorded warning, raise exception if not exists.""" for i, w in enumerate(self._list): if issubclass(w.category, cls): @@ -146,85 +195,79 @@ class WarningsRecorder(warnings.catch_warnings): __tracebackhide__ = True raise AssertionError("%r not found in warning list" % cls) - def clear(self): + def clear(self) -> None: """Clear the list of recorded warnings.""" self._list[:] = [] - def __enter__(self): + # Type ignored because it doesn't exactly warnings.catch_warnings.__enter__ + # -- it returns a List but we only emulate one. + def __enter__(self) -> "WarningsRecorder": # type: ignore if self._entered: __tracebackhide__ = True raise RuntimeError("Cannot enter %r twice" % self) - self._list = super(WarningsRecorder, self).__enter__() + _list = super().__enter__() + # record=True means it's None. + assert _list is not None + self._list = _list warnings.simplefilter("always") - # python3 keeps track of a "filter version", when the filters are - # updated previously seen warnings can be re-warned. python2 has no - # concept of this so we must reset the warnings registry manually. - # trivial patching of `warnings.warn` seems to be enough somehow? - if six.PY2: - - def warn(message, category=None, stacklevel=1): - # duplicate the stdlib logic due to - # bad handing in the c version of warnings - if isinstance(message, Warning): - category = message.__class__ - # Check category argument - if category is None: - category = UserWarning - assert issubclass(category, Warning) - - # emulate resetting the warn registry - f_globals = sys._getframe(stacklevel).f_globals - if "__warningregistry__" in f_globals: - orig = f_globals["__warningregistry__"] - f_globals["__warningregistry__"] = None - try: - return self._saved_warn(message, category, stacklevel + 1) - finally: - f_globals["__warningregistry__"] = orig - else: - return self._saved_warn(message, category, stacklevel + 1) - - warnings.warn, self._saved_warn = warn, warnings.warn return self - def __exit__(self, *exc_info): + def __exit__( + self, + exc_type: Optional["Type[BaseException]"], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: if not self._entered: __tracebackhide__ = True raise RuntimeError("Cannot exit %r without entering first" % self) - # see above where `self._saved_warn` is assigned - if six.PY2: - warnings.warn = self._saved_warn - super(WarningsRecorder, self).__exit__(*exc_info) + + super().__exit__(exc_type, exc_val, exc_tb) # Built-in catch_warnings does not reset entered state so we do it # manually here for this context manager to become reusable. self._entered = False +@final class WarningsChecker(WarningsRecorder): - def __init__(self, expected_warning=None, match_expr=None): - super(WarningsChecker, self).__init__() - - msg = "exceptions must be old-style classes or derived from Warning, not %s" - if isinstance(expected_warning, tuple): + def __init__( + self, + expected_warning: Optional[ + Union["Type[Warning]", Tuple["Type[Warning]", ...]] + ] = None, + match_expr: Optional[Union[str, "Pattern[str]"]] = None, + ) -> None: + super().__init__() + + msg = "exceptions must be derived from Warning, not %s" + if expected_warning is None: + expected_warning_tup = None + elif isinstance(expected_warning, tuple): for exc in expected_warning: - if not inspect.isclass(exc): + if not issubclass(exc, Warning): raise TypeError(msg % type(exc)) - elif inspect.isclass(expected_warning): - expected_warning = (expected_warning,) - elif expected_warning is not None: + expected_warning_tup = expected_warning + elif issubclass(expected_warning, Warning): + expected_warning_tup = (expected_warning,) + else: raise TypeError(msg % type(expected_warning)) - self.expected_warning = expected_warning + self.expected_warning = expected_warning_tup self.match_expr = match_expr - def __exit__(self, *exc_info): - super(WarningsChecker, self).__exit__(*exc_info) + def __exit__( + self, + exc_type: Optional["Type[BaseException]"], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + super().__exit__(exc_type, exc_val, exc_tb) __tracebackhide__ = True # only check if we're not currently handling an exception - if all(a is None for a in exc_info): + if exc_type is None and exc_val is None and exc_tb is None: if self.expected_warning is not None: if not any(issubclass(r.category, self.expected_warning) for r in self): __tracebackhide__ = True diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py index 0bba6762c33..c42f778ec40 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/reports.py @@ -1,10 +1,22 @@ -# -*- coding: utf-8 -*- +from io import StringIO from pprint import pprint - +from typing import Any +from typing import cast +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Tuple +from typing import TypeVar +from typing import Union + +import attr import py -import six +from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo +from _pytest._code.code import ExceptionRepr from _pytest._code.code import ReprEntry from _pytest._code.code import ReprEntryNative from _pytest._code.code import ReprExceptionInfo @@ -13,70 +25,95 @@ from _pytest._code.code import ReprFuncArgs from _pytest._code.code import ReprLocals from _pytest._code.code import ReprTraceback from _pytest._code.code import TerminalRepr +from _pytest._io import TerminalWriter +from _pytest.compat import final +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.nodes import Collector +from _pytest.nodes import Item from _pytest.outcomes import skip from _pytest.pathlib import Path +if TYPE_CHECKING: + from typing import NoReturn + from typing_extensions import Type + from typing_extensions import Literal + + from _pytest.runner import CallInfo -def getslaveinfoline(node): + +def getworkerinfoline(node): try: - return node._slaveinfocache + return node._workerinfocache except AttributeError: - d = node.slaveinfo + d = node.workerinfo ver = "%s.%s.%s" % d["version_info"][:3] - node._slaveinfocache = s = "[%s] %s -- Python %s %s" % ( - d["id"], - d["sysplatform"], - ver, - d["executable"], + node._workerinfocache = s = "[{}] {} -- Python {} {}".format( + d["id"], d["sysplatform"], ver, d["executable"] ) return s -class BaseReport(object): - when = None - location = None +_R = TypeVar("_R", bound="BaseReport") + - def __init__(self, **kw): +class BaseReport: + when = None # type: Optional[str] + location = None # type: Optional[Tuple[str, Optional[int], str]] + longrepr = ( + None + ) # type: Union[None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr] + sections = [] # type: List[Tuple[str, str]] + nodeid = None # type: str + + def __init__(self, **kw: Any) -> None: self.__dict__.update(kw) - def toterminal(self, out): + if TYPE_CHECKING: + # Can have arbitrary fields given to __init__(). + def __getattr__(self, key: str) -> Any: + ... + + def toterminal(self, out: TerminalWriter) -> None: if hasattr(self, "node"): - out.line(getslaveinfoline(self.node)) + out.line(getworkerinfoline(self.node)) longrepr = self.longrepr if longrepr is None: return if hasattr(longrepr, "toterminal"): - longrepr.toterminal(out) + longrepr_terminal = cast(TerminalRepr, longrepr) + longrepr_terminal.toterminal(out) else: try: - out.line(longrepr) + s = str(longrepr) except UnicodeEncodeError: - out.line("<unprintable longrepr>") + s = "<unprintable longrepr>" + out.line(s) - def get_sections(self, prefix): + def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]: for name, content in self.sections: if name.startswith(prefix): yield prefix, content @property - def longreprtext(self): - """ - Read-only property that returns the full string representation - of ``longrepr``. + def longreprtext(self) -> str: + """Read-only property that returns the full string representation of + ``longrepr``. .. versionadded:: 3.0 """ - tw = py.io.TerminalWriter(stringio=True) + file = StringIO() + tw = TerminalWriter(file) tw.hasmarkup = False self.toterminal(tw) - exc = tw.stringio.getvalue() + exc = file.getvalue() return exc.strip() @property - def caplog(self): - """Return captured log lines, if log capturing is enabled + def caplog(self) -> str: + """Return captured log lines, if log capturing is enabled. .. versionadded:: 3.5 """ @@ -85,8 +122,8 @@ class BaseReport(object): ) @property - def capstdout(self): - """Return captured text from stdout, if capturing is enabled + def capstdout(self) -> str: + """Return captured text from stdout, if capturing is enabled. .. versionadded:: 3.0 """ @@ -95,8 +132,8 @@ class BaseReport(object): ) @property - def capstderr(self): - """Return captured text from stderr, if capturing is enabled + def capstderr(self) -> str: + """Return captured text from stderr, if capturing is enabled. .. versionadded:: 3.0 """ @@ -109,16 +146,13 @@ class BaseReport(object): skipped = property(lambda x: x.outcome == "skipped") @property - def fspath(self): + def fspath(self) -> str: return self.nodeid.split("::")[0] @property - def count_towards_summary(self): - """ - **Experimental** - - Returns True if this report should be counted towards the totals shown at the end of the - test session: "1 passed, 1 failure, etc". + def count_towards_summary(self) -> bool: + """**Experimental** Whether this report should be counted towards the + totals shown at the end of the test session: "1 passed, 1 failure, etc". .. note:: @@ -128,12 +162,10 @@ class BaseReport(object): return True @property - def head_line(self): - """ - **Experimental** - - Returns the head line shown with longrepr output for this report, more commonly during - traceback representation during failures:: + def head_line(self) -> Optional[str]: + """**Experimental** The head line shown with longrepr output for this + report, more commonly during traceback representation during + failures:: ________ Test.foo ________ @@ -148,127 +180,43 @@ class BaseReport(object): if self.location is not None: fspath, lineno, domain = self.location return domain + return None - def _get_verbose_word(self, config): + def _get_verbose_word(self, config: Config): _category, _short, verbose = config.hook.pytest_report_teststatus( report=self, config=config ) return verbose - def _to_json(self): - """ - This was originally the serialize_report() function from xdist (ca03269). + def _to_json(self) -> Dict[str, Any]: + """Return the contents of this report as a dict of builtin entries, + suitable for serialization. - Returns the contents of this report as a dict of builtin entries, suitable for - serialization. + This was originally the serialize_report() function from xdist (ca03269). Experimental method. """ - - def disassembled_report(rep): - reprtraceback = rep.longrepr.reprtraceback.__dict__.copy() - reprcrash = rep.longrepr.reprcrash.__dict__.copy() - - new_entries = [] - for entry in reprtraceback["reprentries"]: - entry_data = { - "type": type(entry).__name__, - "data": entry.__dict__.copy(), - } - for key, value in entry_data["data"].items(): - if hasattr(value, "__dict__"): - entry_data["data"][key] = value.__dict__.copy() - new_entries.append(entry_data) - - reprtraceback["reprentries"] = new_entries - - return { - "reprcrash": reprcrash, - "reprtraceback": reprtraceback, - "sections": rep.longrepr.sections, - } - - d = self.__dict__.copy() - if hasattr(self.longrepr, "toterminal"): - if hasattr(self.longrepr, "reprtraceback") and hasattr( - self.longrepr, "reprcrash" - ): - d["longrepr"] = disassembled_report(self) - else: - d["longrepr"] = six.text_type(self.longrepr) - else: - d["longrepr"] = self.longrepr - for name in d: - if isinstance(d[name], (py.path.local, Path)): - d[name] = str(d[name]) - elif name == "result": - d[name] = None # for now - return d + return _report_to_json(self) @classmethod - def _from_json(cls, reportdict): - """ - This was originally the serialize_report() function from xdist (ca03269). + def _from_json(cls: "Type[_R]", reportdict: Dict[str, object]) -> _R: + """Create either a TestReport or CollectReport, depending on the calling class. + + It is the callers responsibility to know which class to pass here. - Factory method that returns either a TestReport or CollectReport, depending on the calling - class. It's the callers responsibility to know which class to pass here. + This was originally the serialize_report() function from xdist (ca03269). Experimental method. """ - if reportdict["longrepr"]: - if ( - "reprcrash" in reportdict["longrepr"] - and "reprtraceback" in reportdict["longrepr"] - ): - - reprtraceback = reportdict["longrepr"]["reprtraceback"] - reprcrash = reportdict["longrepr"]["reprcrash"] - - unserialized_entries = [] - reprentry = None - for entry_data in reprtraceback["reprentries"]: - data = entry_data["data"] - entry_type = entry_data["type"] - if entry_type == "ReprEntry": - reprfuncargs = None - reprfileloc = None - reprlocals = None - if data["reprfuncargs"]: - reprfuncargs = ReprFuncArgs(**data["reprfuncargs"]) - if data["reprfileloc"]: - reprfileloc = ReprFileLocation(**data["reprfileloc"]) - if data["reprlocals"]: - reprlocals = ReprLocals(data["reprlocals"]["lines"]) - - reprentry = ReprEntry( - lines=data["lines"], - reprfuncargs=reprfuncargs, - reprlocals=reprlocals, - filelocrepr=reprfileloc, - style=data["style"], - ) - elif entry_type == "ReprEntryNative": - reprentry = ReprEntryNative(data["lines"]) - else: - _report_unserialization_failure(entry_type, cls, reportdict) - unserialized_entries.append(reprentry) - reprtraceback["reprentries"] = unserialized_entries - - exception_info = ReprExceptionInfo( - reprtraceback=ReprTraceback(**reprtraceback), - reprcrash=ReprFileLocation(**reprcrash), - ) - - for section in reportdict["longrepr"]["sections"]: - exception_info.addsection(*section) - reportdict["longrepr"] = exception_info - - return cls(**reportdict) + kwargs = _report_kwargs_from_json(reportdict) + return cls(**kwargs) -def _report_unserialization_failure(type_name, report_class, reportdict): +def _report_unserialization_failure( + type_name: str, report_class: "Type[BaseReport]", reportdict +) -> "NoReturn": url = "https://github.com/pytest-dev/pytest/issues" - stream = py.io.TextIO() + stream = StringIO() pprint("-" * 100, stream=stream) pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream) pprint("report_name: %s" % report_class, stream=stream) @@ -278,88 +226,89 @@ def _report_unserialization_failure(type_name, report_class, reportdict): raise RuntimeError(stream.getvalue()) +@final class TestReport(BaseReport): - """ Basic test report object (also used for setup and teardown calls if - they fail). - """ + """Basic test report object (also used for setup and teardown calls if + they fail).""" __test__ = False def __init__( self, - nodeid, - location, + nodeid: str, + location: Tuple[str, Optional[int], str], keywords, - outcome, - longrepr, - when, - sections=(), - duration=0, - user_properties=None, + outcome: "Literal['passed', 'failed', 'skipped']", + longrepr: Union[ + None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr + ], + when: "Literal['setup', 'call', 'teardown']", + sections: Iterable[Tuple[str, str]] = (), + duration: float = 0, + user_properties: Optional[Iterable[Tuple[str, object]]] = None, **extra - ): - #: normalized collection node id + ) -> None: + #: Normalized collection nodeid. self.nodeid = nodeid - #: a (filesystempath, lineno, domaininfo) tuple indicating the + #: A (filesystempath, lineno, domaininfo) tuple indicating the #: actual location of a test item - it might be different from the #: collected one e.g. if a method is inherited from a different module. - self.location = location + self.location = location # type: Tuple[str, Optional[int], str] - #: a name -> value dictionary containing all keywords and + #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. self.keywords = keywords - #: test outcome, always one of "passed", "failed", "skipped". + #: Test outcome, always one of "passed", "failed", "skipped". self.outcome = outcome #: None or a failure representation. self.longrepr = longrepr - #: one of 'setup', 'call', 'teardown' to indicate runtest phase. + #: One of 'setup', 'call', 'teardown' to indicate runtest phase. self.when = when - #: user properties is a list of tuples (name, value) that holds user - #: defined properties of the test + #: User properties is a list of tuples (name, value) that holds user + #: defined properties of the test. self.user_properties = list(user_properties or []) - #: list of pairs ``(str, str)`` of extra information which needs to + #: List of pairs ``(str, str)`` of extra information which needs to #: marshallable. Used by pytest to add captured text #: from ``stdout`` and ``stderr``, but may be used by other plugins #: to add arbitrary information to reports. self.sections = list(sections) - #: time it took to run just the test + #: Time it took to run just the test. self.duration = duration self.__dict__.update(extra) - def __repr__(self): - return "<%s %r when=%r outcome=%r>" % ( - self.__class__.__name__, - self.nodeid, - self.when, - self.outcome, + def __repr__(self) -> str: + return "<{} {!r} when={!r} outcome={!r}>".format( + self.__class__.__name__, self.nodeid, self.when, self.outcome ) @classmethod - def from_item_and_call(cls, item, call): - """ - Factory method to create and fill a TestReport with standard item and call info. - """ + def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": + """Create and fill a TestReport with standard item and call info.""" when = call.when - duration = call.stop - call.start + # Remove "collect" from the Literal type -- only for collection calls. + assert when != "collect" + duration = call.duration keywords = {x: 1 for x in item.keywords} excinfo = call.excinfo sections = [] if not call.excinfo: - outcome = "passed" - longrepr = None + outcome = "passed" # type: Literal["passed", "failed", "skipped"] + longrepr = ( + None + ) # type: Union[None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr] else: if not isinstance(excinfo, ExceptionInfo): outcome = "failed" longrepr = excinfo - elif excinfo.errisinstance(skip.Exception): + elif isinstance(excinfo.value, skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() longrepr = (str(r.path), r.lineno, r.message) @@ -372,7 +321,7 @@ class TestReport(BaseReport): excinfo, style=item.config.getoption("tbstyle", "auto") ) for rwhen, key, content in item._report_sections: - sections.append(("Captured %s %s" % (key, rwhen), content)) + sections.append(("Captured {} {}".format(key, rwhen), content)) return cls( item.nodeid, item.location, @@ -386,50 +335,234 @@ class TestReport(BaseReport): ) +@final class CollectReport(BaseReport): + """Collection report object.""" + when = "collect" - def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra): + def __init__( + self, + nodeid: str, + outcome: "Literal['passed', 'skipped', 'failed']", + longrepr, + result: Optional[List[Union[Item, Collector]]], + sections: Iterable[Tuple[str, str]] = (), + **extra + ) -> None: + #: Normalized collection nodeid. self.nodeid = nodeid + + #: Test outcome, always one of "passed", "failed", "skipped". self.outcome = outcome + + #: None or a failure representation. self.longrepr = longrepr + + #: The collected items and collection nodes. self.result = result or [] + + #: List of pairs ``(str, str)`` of extra information which needs to + #: marshallable. + # Used by pytest to add captured text : from ``stdout`` and ``stderr``, + # but may be used by other plugins : to add arbitrary information to + # reports. self.sections = list(sections) + self.__dict__.update(extra) @property def location(self): return (self.fspath, None, self.fspath) - def __repr__(self): - return "<CollectReport %r lenresult=%s outcome=%r>" % ( - self.nodeid, - len(self.result), - self.outcome, + def __repr__(self) -> str: + return "<CollectReport {!r} lenresult={} outcome={!r}>".format( + self.nodeid, len(self.result), self.outcome ) class CollectErrorRepr(TerminalRepr): - def __init__(self, msg): + def __init__(self, msg: str) -> None: self.longrepr = msg - def toterminal(self, out): + def toterminal(self, out: TerminalWriter) -> None: out.line(self.longrepr, red=True) -def pytest_report_to_serializable(report): +def pytest_report_to_serializable( + report: Union[CollectReport, TestReport] +) -> Optional[Dict[str, Any]]: if isinstance(report, (TestReport, CollectReport)): data = report._to_json() - data["_report_type"] = report.__class__.__name__ + data["$report_type"] = report.__class__.__name__ return data + # TODO: Check if this is actually reachable. + return None # type: ignore[unreachable] -def pytest_report_from_serializable(data): - if "_report_type" in data: - if data["_report_type"] == "TestReport": +def pytest_report_from_serializable( + data: Dict[str, Any], +) -> Optional[Union[CollectReport, TestReport]]: + if "$report_type" in data: + if data["$report_type"] == "TestReport": return TestReport._from_json(data) - elif data["_report_type"] == "CollectReport": + elif data["$report_type"] == "CollectReport": return CollectReport._from_json(data) assert False, "Unknown report_type unserialize data: {}".format( - data["_report_type"] + data["$report_type"] ) + return None + + +def _report_to_json(report: BaseReport) -> Dict[str, Any]: + """Return the contents of this report as a dict of builtin entries, + suitable for serialization. + + This was originally the serialize_report() function from xdist (ca03269). + """ + + def serialize_repr_entry( + entry: Union[ReprEntry, ReprEntryNative] + ) -> Dict[str, Any]: + data = attr.asdict(entry) + for key, value in data.items(): + if hasattr(value, "__dict__"): + data[key] = attr.asdict(value) + entry_data = {"type": type(entry).__name__, "data": data} + return entry_data + + def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: + result = attr.asdict(reprtraceback) + result["reprentries"] = [ + serialize_repr_entry(x) for x in reprtraceback.reprentries + ] + return result + + def serialize_repr_crash( + reprcrash: Optional[ReprFileLocation], + ) -> Optional[Dict[str, Any]]: + if reprcrash is not None: + return attr.asdict(reprcrash) + else: + return None + + def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: + assert rep.longrepr is not None + # TODO: Investigate whether the duck typing is really necessary here. + longrepr = cast(ExceptionRepr, rep.longrepr) + result = { + "reprcrash": serialize_repr_crash(longrepr.reprcrash), + "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), + "sections": longrepr.sections, + } # type: Dict[str, Any] + if isinstance(longrepr, ExceptionChainRepr): + result["chain"] = [] + for repr_traceback, repr_crash, description in longrepr.chain: + result["chain"].append( + ( + serialize_repr_traceback(repr_traceback), + serialize_repr_crash(repr_crash), + description, + ) + ) + else: + result["chain"] = None + return result + + d = report.__dict__.copy() + if hasattr(report.longrepr, "toterminal"): + if hasattr(report.longrepr, "reprtraceback") and hasattr( + report.longrepr, "reprcrash" + ): + d["longrepr"] = serialize_exception_longrepr(report) + else: + d["longrepr"] = str(report.longrepr) + else: + d["longrepr"] = report.longrepr + for name in d: + if isinstance(d[name], (py.path.local, Path)): + d[name] = str(d[name]) + elif name == "result": + d[name] = None # for now + return d + + +def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: + """Return **kwargs that can be used to construct a TestReport or + CollectReport instance. + + This was originally the serialize_report() function from xdist (ca03269). + """ + + def deserialize_repr_entry(entry_data): + data = entry_data["data"] + entry_type = entry_data["type"] + if entry_type == "ReprEntry": + reprfuncargs = None + reprfileloc = None + reprlocals = None + if data["reprfuncargs"]: + reprfuncargs = ReprFuncArgs(**data["reprfuncargs"]) + if data["reprfileloc"]: + reprfileloc = ReprFileLocation(**data["reprfileloc"]) + if data["reprlocals"]: + reprlocals = ReprLocals(data["reprlocals"]["lines"]) + + reprentry = ReprEntry( + lines=data["lines"], + reprfuncargs=reprfuncargs, + reprlocals=reprlocals, + reprfileloc=reprfileloc, + style=data["style"], + ) # type: Union[ReprEntry, ReprEntryNative] + elif entry_type == "ReprEntryNative": + reprentry = ReprEntryNative(data["lines"]) + else: + _report_unserialization_failure(entry_type, TestReport, reportdict) + return reprentry + + def deserialize_repr_traceback(repr_traceback_dict): + repr_traceback_dict["reprentries"] = [ + deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"] + ] + return ReprTraceback(**repr_traceback_dict) + + def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): + if repr_crash_dict is not None: + return ReprFileLocation(**repr_crash_dict) + else: + return None + + if ( + reportdict["longrepr"] + and "reprcrash" in reportdict["longrepr"] + and "reprtraceback" in reportdict["longrepr"] + ): + + reprtraceback = deserialize_repr_traceback( + reportdict["longrepr"]["reprtraceback"] + ) + reprcrash = deserialize_repr_crash(reportdict["longrepr"]["reprcrash"]) + if reportdict["longrepr"]["chain"]: + chain = [] + for repr_traceback_data, repr_crash_data, description in reportdict[ + "longrepr" + ]["chain"]: + chain.append( + ( + deserialize_repr_traceback(repr_traceback_data), + deserialize_repr_crash(repr_crash_data), + description, + ) + ) + exception_info = ExceptionChainRepr( + chain + ) # type: Union[ExceptionChainRepr,ReprExceptionInfo] + else: + exception_info = ReprExceptionInfo(reprtraceback, reprcrash) + + for section in reportdict["longrepr"]["sections"]: + exception_info.addsection(*section) + reportdict["longrepr"] = exception_info + + return reportdict diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/resultlog.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/resultlog.py deleted file mode 100644 index bd30b5071e9..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/resultlog.py +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 -*- -""" log machine-parseable test session result information in a plain -text file. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -import py - - -def pytest_addoption(parser): - group = parser.getgroup("terminal reporting", "resultlog plugin options") - group.addoption( - "--resultlog", - "--result-log", - action="store", - metavar="path", - default=None, - help="DEPRECATED path for machine-readable result log.", - ) - - -def pytest_configure(config): - resultlog = config.option.resultlog - # prevent opening resultlog on slave nodes (xdist) - if resultlog and not hasattr(config, "slaveinput"): - dirname = os.path.dirname(os.path.abspath(resultlog)) - if not os.path.isdir(dirname): - os.makedirs(dirname) - logfile = open(resultlog, "w", 1) # line buffered - config._resultlog = ResultLog(config, logfile) - config.pluginmanager.register(config._resultlog) - - from _pytest.deprecated import RESULT_LOG - from _pytest.warnings import _issue_warning_captured - - _issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2) - - -def pytest_unconfigure(config): - resultlog = getattr(config, "_resultlog", None) - if resultlog: - resultlog.logfile.close() - del config._resultlog - config.pluginmanager.unregister(resultlog) - - -class ResultLog(object): - def __init__(self, config, logfile): - self.config = config - self.logfile = logfile # preferably line buffered - - def write_log_entry(self, testpath, lettercode, longrepr): - print("%s %s" % (lettercode, testpath), file=self.logfile) - for line in longrepr.splitlines(): - print(" %s" % line, file=self.logfile) - - def log_outcome(self, report, lettercode, longrepr): - testpath = getattr(report, "nodeid", None) - if testpath is None: - testpath = report.fspath - self.write_log_entry(testpath, lettercode, longrepr) - - def pytest_runtest_logreport(self, report): - if report.when != "call" and report.passed: - return - res = self.config.hook.pytest_report_teststatus( - report=report, config=self.config - ) - code = res[1] - if code == "x": - longrepr = str(report.longrepr) - elif code == "X": - longrepr = "" - elif report.passed: - longrepr = "" - elif report.failed: - longrepr = str(report.longrepr) - elif report.skipped: - longrepr = str(report.longrepr[2]) - self.log_outcome(report, code, longrepr) - - def pytest_collectreport(self, report): - if not report.passed: - if report.failed: - code = "F" - longrepr = str(report.longrepr) - else: - assert report.skipped - code = "S" - longrepr = "%s:%d: %s" % report.longrepr - self.log_outcome(report, code, longrepr) - - def pytest_internalerror(self, excrepr): - reprcrash = getattr(excrepr, "reprcrash", None) - path = getattr(reprcrash, "path", None) - if path is None: - path = "cwd:%s" % py.path.local() - self.write_log_entry(path, "!", str(excrepr)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py index d51e859f1d1..f29d356fe07 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/runner.py @@ -1,30 +1,49 @@ -# -*- coding: utf-8 -*- -""" basic collect and runtest protocol implementations """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Basic collect and runtest protocol implementations.""" import bdb import os import sys -from time import time +from typing import Callable +from typing import cast +from typing import Dict +from typing import Generic +from typing import List +from typing import Optional +from typing import Tuple +from typing import TypeVar +from typing import Union import attr -import six +from .reports import BaseReport from .reports import CollectErrorRepr from .reports import CollectReport from .reports import TestReport +from _pytest import timing +from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo +from _pytest._code.code import TerminalRepr +from _pytest.compat import final +from _pytest.compat import TYPE_CHECKING +from _pytest.config.argparsing import Parser +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.nodes import Node from _pytest.outcomes import Exit from _pytest.outcomes import Skipped from _pytest.outcomes import TEST_OUTCOME +if TYPE_CHECKING: + from typing import Type + from typing_extensions import Literal + + from _pytest.main import Session + from _pytest.terminal import TerminalReporter + # -# pytest plugin hooks +# pytest plugin hooks. -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "reporting", after="general") group.addoption( "--durations", @@ -33,11 +52,20 @@ def pytest_addoption(parser): default=None, metavar="N", help="show N slowest setup/test durations (N=0 for all).", - ), + ) + group.addoption( + "--durations-min", + action="store", + type=float, + default=0.005, + metavar="N", + help="Minimal duration in seconds for inclusion in slowest list. Default 0.005", + ) -def pytest_terminal_summary(terminalreporter): +def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: durations = terminalreporter.config.option.durations + durations_min = terminalreporter.config.option.durations_min verbose = terminalreporter.config.getvalue("verbose") if durations is None: return @@ -52,38 +80,44 @@ def pytest_terminal_summary(terminalreporter): dlist.sort(key=lambda x: x.duration) dlist.reverse() if not durations: - tr.write_sep("=", "slowest test durations") + tr.write_sep("=", "slowest durations") else: - tr.write_sep("=", "slowest %s test durations" % durations) + tr.write_sep("=", "slowest %s durations" % durations) dlist = dlist[:durations] - for rep in dlist: - if verbose < 2 and rep.duration < 0.005: + for i, rep in enumerate(dlist): + if verbose < 2 and rep.duration < durations_min: tr.write_line("") - tr.write_line("(0.00 durations hidden. Use -vv to show these durations.)") + tr.write_line( + "(%s durations < %gs hidden. Use -vv to show these durations.)" + % (len(dlist) - i, durations_min) + ) break - tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, rep.nodeid)) + tr.write_line("{:02.2f}s {:<8} {}".format(rep.duration, rep.when, rep.nodeid)) -def pytest_sessionstart(session): +def pytest_sessionstart(session: "Session") -> None: session._setupstate = SetupState() -def pytest_sessionfinish(session): +def pytest_sessionfinish(session: "Session") -> None: session._setupstate.teardown_all() -def pytest_runtest_protocol(item, nextitem): - item.ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) +def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: + ihook = item.ihook + ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) runtestprotocol(item, nextitem=nextitem) - item.ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location) + ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location) return True -def runtestprotocol(item, log=True, nextitem=None): +def runtestprotocol( + item: Item, log: bool = True, nextitem: Optional[Item] = None +) -> List[TestReport]: hasrequest = hasattr(item, "_request") - if hasrequest and not item._request: - item._initrequest() + if hasrequest and not item._request: # type: ignore[attr-defined] + item._initrequest() # type: ignore[attr-defined] rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: @@ -92,57 +126,63 @@ def runtestprotocol(item, log=True, nextitem=None): if not item.config.getoption("setuponly", False): reports.append(call_and_report(item, "call", log)) reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) - # after all teardown hooks have been called - # want funcargs and request info to go away + # After all teardown hooks have been called + # want funcargs and request info to go away. if hasrequest: - item._request = False - item.funcargs = None + item._request = False # type: ignore[attr-defined] + item.funcargs = None # type: ignore[attr-defined] return reports -def show_test_item(item): +def show_test_item(item: Item) -> None: """Show test function, parameters and the fixtures of the test item.""" tw = item.config.get_terminal_writer() tw.line() tw.write(" " * 8) - tw.write(item._nodeid) - used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) + tw.write(item.nodeid) + used_fixtures = sorted(getattr(item, "fixturenames", [])) if used_fixtures: tw.write(" (fixtures used: {})".format(", ".join(used_fixtures))) + tw.flush() -def pytest_runtest_setup(item): +def pytest_runtest_setup(item: Item) -> None: _update_current_test_var(item, "setup") item.session._setupstate.prepare(item) -def pytest_runtest_call(item): +def pytest_runtest_call(item: Item) -> None: _update_current_test_var(item, "call") - sys.last_type, sys.last_value, sys.last_traceback = (None, None, None) + try: + del sys.last_type + del sys.last_value + del sys.last_traceback + except AttributeError: + pass try: item.runtest() - except Exception: + except Exception as e: # Store trace info to allow postmortem debugging - type, value, tb = sys.exc_info() - tb = tb.tb_next # Skip *this* frame - sys.last_type = type - sys.last_value = value - sys.last_traceback = tb - del type, value, tb # Get rid of these in this frame - raise + sys.last_type = type(e) + sys.last_value = e + assert e.__traceback__ is not None + # Skip *this* frame + sys.last_traceback = e.__traceback__.tb_next + raise e -def pytest_runtest_teardown(item, nextitem): +def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: _update_current_test_var(item, "teardown") item.session._setupstate.teardown_exact(item, nextitem) _update_current_test_var(item, None) -def _update_current_test_var(item, when): - """ - Update PYTEST_CURRENT_TEST to reflect the current item and stage. +def _update_current_test_var( + item: Item, when: Optional["Literal['setup', 'call', 'teardown']"] +) -> None: + """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. - If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment. + If ``when`` is None, delete ``PYTEST_CURRENT_TEST`` from the environment. """ var_name = "PYTEST_CURRENT_TEST" if when: @@ -154,7 +194,7 @@ def _update_current_test_var(item, when): os.environ.pop(var_name) -def pytest_report_teststatus(report): +def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: if report.when in ("setup", "teardown"): if report.failed: # category, shortletter, verbose-word @@ -163,16 +203,19 @@ def pytest_report_teststatus(report): return "skipped", "s", "SKIPPED" else: return "", "", "" + return None # # Implementation -def call_and_report(item, when, log=True, **kwds): +def call_and_report( + item: Item, when: "Literal['setup', 'call', 'teardown']", log: bool = True, **kwds +) -> TestReport: call = call_runtest_hook(item, when, **kwds) hook = item.ihook - report = hook.pytest_runtest_makereport(item=item, call=call) + report = hook.pytest_runtest_makereport(item=item, call=call) # type: TestReport if log: hook.pytest_runtest_logreport(report=report) if check_interactive_exception(call, report): @@ -180,18 +223,33 @@ def call_and_report(item, when, log=True, **kwds): return report -def check_interactive_exception(call, report): - return call.excinfo and not ( - hasattr(report, "wasxfail") - or call.excinfo.errisinstance(Skipped) - or call.excinfo.errisinstance(bdb.BdbQuit) - ) +def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool: + """Check whether the call raised an exception that should be reported as + interactive.""" + if call.excinfo is None: + # Didn't raise. + return False + if hasattr(report, "wasxfail"): + # Exception was expected. + return False + if isinstance(call.excinfo.value, (Skipped, bdb.BdbQuit)): + # Special control flow exception. + return False + return True -def call_runtest_hook(item, when, **kwds): - hookname = "pytest_runtest_" + when - ihook = getattr(item.ihook, hookname) - reraise = (Exit,) +def call_runtest_hook( + item: Item, when: "Literal['setup', 'call', 'teardown']", **kwds +) -> "CallInfo[None]": + if when == "setup": + ihook = item.ihook.pytest_runtest_setup # type: Callable[..., None] + elif when == "call": + ihook = item.ihook.pytest_runtest_call + elif when == "teardown": + ihook = item.ihook.pytest_runtest_teardown + else: + assert False, "Unhandled runtest hook case: {}".format(when) + reraise = (Exit,) # type: Tuple[Type[BaseException], ...] if not item.config.getoption("usepdb", False): reraise += (KeyboardInterrupt,) return CallInfo.from_call( @@ -199,94 +257,125 @@ def call_runtest_hook(item, when, **kwds): ) +TResult = TypeVar("TResult", covariant=True) + + +@final @attr.s(repr=False) -class CallInfo(object): - """ Result/Exception info a function invocation. """ +class CallInfo(Generic[TResult]): + """Result/Exception info a function invocation. + + :param T result: + The return value of the call, if it didn't raise. Can only be + accessed if excinfo is None. + :param Optional[ExceptionInfo] excinfo: + The captured exception of the call, if it raised. + :param float start: + The system time when the call started, in seconds since the epoch. + :param float stop: + The system time when the call ended, in seconds since the epoch. + :param float duration: + The call duration, in seconds. + :param str when: + The context of invocation: "setup", "call", "teardown", ... + """ - _result = attr.ib() - # Optional[ExceptionInfo] - excinfo = attr.ib() - start = attr.ib() - stop = attr.ib() - when = attr.ib() + _result = attr.ib(type="Optional[TResult]") + excinfo = attr.ib(type=Optional[ExceptionInfo[BaseException]]) + start = attr.ib(type=float) + stop = attr.ib(type=float) + duration = attr.ib(type=float) + when = attr.ib(type="Literal['collect', 'setup', 'call', 'teardown']") @property - def result(self): + def result(self) -> TResult: if self.excinfo is not None: raise AttributeError("{!r} has no valid result".format(self)) - return self._result + # The cast is safe because an exception wasn't raised, hence + # _result has the expected function return type (which may be + # None, that's why a cast and not an assert). + return cast(TResult, self._result) @classmethod - def from_call(cls, func, when, reraise=None): - #: context of invocation: one of "setup", "call", - #: "teardown", "memocollect" - start = time() + def from_call( + cls, + func: "Callable[[], TResult]", + when: "Literal['collect', 'setup', 'call', 'teardown']", + reraise: "Optional[Union[Type[BaseException], Tuple[Type[BaseException], ...]]]" = None, + ) -> "CallInfo[TResult]": excinfo = None + start = timing.time() + precise_start = timing.perf_counter() try: - result = func() - except: # noqa + result = func() # type: Optional[TResult] + except BaseException: excinfo = ExceptionInfo.from_current() - if reraise is not None and excinfo.errisinstance(reraise): + if reraise is not None and isinstance(excinfo.value, reraise): raise result = None - stop = time() - return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo) - - def __repr__(self): - if self.excinfo is not None: - status = "exception" - value = self.excinfo.value - else: - # TODO: investigate unification - value = repr(self._result) - status = "result" - return "<CallInfo when={when!r} {status}: {value}>".format( - when=self.when, value=value, status=status + # use the perf counter + precise_stop = timing.perf_counter() + duration = precise_stop - precise_start + stop = timing.time() + return cls( + start=start, + stop=stop, + duration=duration, + when=when, + result=result, + excinfo=excinfo, ) + def __repr__(self) -> str: + if self.excinfo is None: + return "<CallInfo when={!r} result: {!r}>".format(self.when, self._result) + return "<CallInfo when={!r} excinfo={!r}>".format(self.when, self.excinfo) + -def pytest_runtest_makereport(item, call): +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: return TestReport.from_item_and_call(item, call) -def pytest_make_collect_report(collector): +def pytest_make_collect_report(collector: Collector) -> CollectReport: call = CallInfo.from_call(lambda: list(collector.collect()), "collect") - longrepr = None + longrepr = None # type: Union[None, Tuple[str, int, str], str, TerminalRepr] if not call.excinfo: - outcome = "passed" + outcome = "passed" # type: Literal["passed", "skipped", "failed"] else: - from _pytest import nose - - skip_exceptions = (Skipped,) + nose.get_skip_exceptions() - if call.excinfo.errisinstance(skip_exceptions): + skip_exceptions = [Skipped] + unittest = sys.modules.get("unittest") + if unittest is not None: + # Type ignored because unittest is loaded dynamically. + skip_exceptions.append(unittest.SkipTest) # type: ignore + if isinstance(call.excinfo.value, tuple(skip_exceptions)): outcome = "skipped" - r = collector._repr_failure_py(call.excinfo, "line").reprcrash + r_ = collector._repr_failure_py(call.excinfo, "line") + assert isinstance(r_, ExceptionChainRepr), repr(r_) + r = r_.reprcrash + assert r longrepr = (str(r.path), r.lineno, r.message) else: outcome = "failed" errorinfo = collector.repr_failure(call.excinfo) if not hasattr(errorinfo, "toterminal"): + assert isinstance(errorinfo, str) errorinfo = CollectErrorRepr(errorinfo) longrepr = errorinfo - rep = CollectReport( - collector.nodeid, outcome, longrepr, getattr(call, "result", None) - ) - rep.call = call # see collect_one_node + result = call.result if not call.excinfo else None + rep = CollectReport(collector.nodeid, outcome, longrepr, result) + rep.call = call # type: ignore # see collect_one_node return rep -class SetupState(object): - """ shared state for setting up/tearing down test items or collectors. """ +class SetupState: + """Shared state for setting up/tearing down test items or collectors.""" def __init__(self): - self.stack = [] - self._finalizers = {} - - def addfinalizer(self, finalizer, colitem): - """ attach a finalizer to the given colitem. - if colitem is None, this will add a finalizer that - is called at the end of teardown_all(). - """ + self.stack = [] # type: List[Node] + self._finalizers = {} # type: Dict[Node, List[Callable[[], object]]] + + def addfinalizer(self, finalizer: Callable[[], object], colitem) -> None: + """Attach a finalizer to the given colitem.""" assert colitem and not isinstance(colitem, tuple) assert callable(finalizer) # assert colitem in self.stack # some unit tests don't setup stack :/ @@ -296,79 +385,76 @@ class SetupState(object): colitem = self.stack.pop() self._teardown_with_finalization(colitem) - def _callfinalizers(self, colitem): + def _callfinalizers(self, colitem) -> None: finalizers = self._finalizers.pop(colitem, None) exc = None while finalizers: fin = finalizers.pop() try: fin() - except TEST_OUTCOME: + except TEST_OUTCOME as e: # XXX Only first exception will be seen by user, # ideally all should be reported. if exc is None: - exc = sys.exc_info() + exc = e if exc: - six.reraise(*exc) + raise exc - def _teardown_with_finalization(self, colitem): + def _teardown_with_finalization(self, colitem) -> None: self._callfinalizers(colitem) - if hasattr(colitem, "teardown"): - colitem.teardown() + colitem.teardown() for colitem in self._finalizers: - assert ( - colitem is None or colitem in self.stack or isinstance(colitem, tuple) - ) + assert colitem in self.stack - def teardown_all(self): + def teardown_all(self) -> None: while self.stack: self._pop_and_teardown() for key in list(self._finalizers): self._teardown_with_finalization(key) assert not self._finalizers - def teardown_exact(self, item, nextitem): + def teardown_exact(self, item, nextitem) -> None: needed_collectors = nextitem and nextitem.listchain() or [] self._teardown_towards(needed_collectors) - def _teardown_towards(self, needed_collectors): + def _teardown_towards(self, needed_collectors) -> None: exc = None while self.stack: if self.stack == needed_collectors[: len(self.stack)]: break try: self._pop_and_teardown() - except TEST_OUTCOME: + except TEST_OUTCOME as e: # XXX Only first exception will be seen by user, # ideally all should be reported. if exc is None: - exc = sys.exc_info() + exc = e if exc: - six.reraise(*exc) + raise exc - def prepare(self, colitem): - """ setup objects along the collector chain to the test-method - and teardown previously setup objects.""" - needed_collectors = colitem.listchain() - self._teardown_towards(needed_collectors) + def prepare(self, colitem) -> None: + """Setup objects along the collector chain to the test-method.""" - # check if the last collection node has raised an error + # Check if the last collection node has raised an error. for col in self.stack: if hasattr(col, "_prepare_exc"): - six.reraise(*col._prepare_exc) + exc = col._prepare_exc # type: ignore[attr-defined] + raise exc + + needed_collectors = colitem.listchain() for col in needed_collectors[len(self.stack) :]: self.stack.append(col) try: col.setup() - except TEST_OUTCOME: - col._prepare_exc = sys.exc_info() - raise + except TEST_OUTCOME as e: + col._prepare_exc = e # type: ignore[attr-defined] + raise e -def collect_one_node(collector): +def collect_one_node(collector: Collector) -> CollectReport: ihook = collector.ihook ihook.pytest_collectstart(collector=collector) - rep = ihook.pytest_make_collect_report(collector=collector) + rep = ihook.pytest_make_collect_report(collector=collector) # type: CollectReport call = rep.__dict__.pop("call", None) if call and check_interactive_exception(call, rep): ihook.pytest_exception_interact(node=collector, call=call, report=rep) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py index 0859011241a..44a1094c0d2 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setuponly.py @@ -1,14 +1,17 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import sys +from typing import Generator +from typing import Optional +from typing import Union import pytest +from _pytest._io.saferepr import saferepr +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureDef +from _pytest.fixtures import SubRequest -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--setuponly", @@ -25,38 +28,39 @@ def pytest_addoption(parser): @pytest.hookimpl(hookwrapper=True) -def pytest_fixture_setup(fixturedef, request): +def pytest_fixture_setup( + fixturedef: FixtureDef[object], request: SubRequest +) -> Generator[None, None, None]: yield - config = request.config - if config.option.setupshow: + if request.config.option.setupshow: if hasattr(request, "param"): # Save the fixture parameter so ._show_fixture_action() can # display it now and during the teardown (in .finish()). if fixturedef.ids: if callable(fixturedef.ids): - fixturedef.cached_param = fixturedef.ids(request.param) + param = fixturedef.ids(request.param) else: - fixturedef.cached_param = fixturedef.ids[request.param_index] + param = fixturedef.ids[request.param_index] else: - fixturedef.cached_param = request.param + param = request.param + fixturedef.cached_param = param # type: ignore[attr-defined] _show_fixture_action(fixturedef, "SETUP") -def pytest_fixture_post_finalizer(fixturedef): - if hasattr(fixturedef, "cached_result"): +def pytest_fixture_post_finalizer(fixturedef: FixtureDef[object]) -> None: + if fixturedef.cached_result is not None: config = fixturedef._fixturemanager.config if config.option.setupshow: _show_fixture_action(fixturedef, "TEARDOWN") if hasattr(fixturedef, "cached_param"): - del fixturedef.cached_param + del fixturedef.cached_param # type: ignore[attr-defined] -def _show_fixture_action(fixturedef, msg): +def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None: config = fixturedef._fixturemanager.config capman = config.pluginmanager.getplugin("capturemanager") if capman: capman.suspend_global_capture() - out, err = capman.read_global_capture() tw = config.get_terminal_writer() tw.line() @@ -75,15 +79,16 @@ def _show_fixture_action(fixturedef, msg): tw.write(" (fixtures used: {})".format(", ".join(deps))) if hasattr(fixturedef, "cached_param"): - tw.write("[{}]".format(fixturedef.cached_param)) + tw.write("[{}]".format(saferepr(fixturedef.cached_param, maxsize=42))) # type: ignore[attr-defined] + + tw.flush() if capman: capman.resume_global_capture() - sys.stdout.write(out) - sys.stderr.write(err) @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.setuponly: config.option.setupshow = True + return None diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py index 47b0fe82ef6..9ba81ccaf0a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/setupplan.py @@ -1,12 +1,15 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +from typing import Optional +from typing import Union import pytest +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser +from _pytest.fixtures import FixtureDef +from _pytest.fixtures import SubRequest -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("debugconfig") group.addoption( "--setupplan", @@ -18,15 +21,20 @@ def pytest_addoption(parser): @pytest.hookimpl(tryfirst=True) -def pytest_fixture_setup(fixturedef, request): +def pytest_fixture_setup( + fixturedef: FixtureDef[object], request: SubRequest +) -> Optional[object]: # Will return a dummy fixture if the setuponly option is provided. if request.config.option.setupplan: - fixturedef.cached_result = (None, None, None) + my_cache_key = fixturedef.cache_key(request) + fixturedef.cached_result = (None, my_cache_key, None) return fixturedef.cached_result + return None @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config): +def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: if config.option.setupplan: config.option.setuponly = True config.option.setupshow = True + return None diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py index bc8b88e7178..c5b4ff39e85 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/skipping.py @@ -1,17 +1,32 @@ -# -*- coding: utf-8 -*- -""" support for skip/xfail functions and markers. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +"""Support for skip/xfail functions and markers.""" +import os +import platform +import sys +import traceback +from typing import Generator +from typing import Optional +from typing import Tuple +import attr + +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config from _pytest.config import hookimpl -from _pytest.mark.evaluate import MarkEvaluator +from _pytest.config.argparsing import Parser +from _pytest.mark.structures import Mark +from _pytest.nodes import Item from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.outcomes import xfail +from _pytest.reports import BaseReport +from _pytest.runner import CallInfo +from _pytest.store import StoreKey + +if TYPE_CHECKING: + from typing import Type -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--runxfail", @@ -30,7 +45,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: if config.option.runxfail: # yay a hack import pytest @@ -41,7 +56,7 @@ def pytest_configure(config): def nop(*args, **kwargs): pass - nop.Exception = xfail.Exception + nop.Exception = xfail.Exception # type: ignore[attr-defined] setattr(pytest, "xfail", nop) config.addinivalue_line( @@ -52,135 +67,250 @@ def pytest_configure(config): ) config.addinivalue_line( "markers", - "skipif(condition): skip the given test function if eval(condition) " - "results in a True value. Evaluation happens within the " - "module global context. Example: skipif('sys.platform == \"win32\"') " - "skips the test if we are on the win32 platform. see " - "https://docs.pytest.org/en/latest/skipping.html", + "skipif(condition, ..., *, reason=...): " + "skip the given test function if any of the conditions evaluate to True. " + "Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. " + "See https://docs.pytest.org/en/stable/reference.html#pytest-mark-skipif", ) config.addinivalue_line( "markers", - "xfail(condition, reason=None, run=True, raises=None, strict=False): " - "mark the test function as an expected failure if eval(condition) " - "has a True value. Optionally specify a reason for better reporting " + "xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): " + "mark the test function as an expected failure if any of the conditions " + "evaluate to True. Optionally specify a reason for better reporting " "and run=False if you don't even want to execute the test function. " "If only specific exception(s) are expected, you can list them in " "raises, and if the test fails in other ways, it will be reported as " - "a true failure. See https://docs.pytest.org/en/latest/skipping.html", + "a true failure. See https://docs.pytest.org/en/stable/reference.html#pytest-mark-xfail", ) -@hookimpl(tryfirst=True) -def pytest_runtest_setup(item): - # Check if skip or skipif are specified as pytest marks - item._skipped_by_mark = False - eval_skipif = MarkEvaluator(item, "skipif") - if eval_skipif.istrue(): - item._skipped_by_mark = True - skip(eval_skipif.getexplanation()) - - for skip_info in item.iter_markers(name="skip"): - item._skipped_by_mark = True - if "reason" in skip_info.kwargs: - skip(skip_info.kwargs["reason"]) - elif skip_info.args: - skip(skip_info.args[0]) +def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]: + """Evaluate a single skipif/xfail condition. + + If an old-style string condition is given, it is eval()'d, otherwise the + condition is bool()'d. If this fails, an appropriately formatted pytest.fail + is raised. + + Returns (result, reason). The reason is only relevant if the result is True. + """ + # String condition. + if isinstance(condition, str): + globals_ = { + "os": os, + "sys": sys, + "platform": platform, + "config": item.config, + } + if hasattr(item, "obj"): + globals_.update(item.obj.__globals__) # type: ignore[attr-defined] + try: + filename = "<{} condition>".format(mark.name) + condition_code = compile(condition, filename, "eval") + result = eval(condition_code, globals_) + except SyntaxError as exc: + msglines = [ + "Error evaluating %r condition" % mark.name, + " " + condition, + " " + " " * (exc.offset or 0) + "^", + "SyntaxError: invalid syntax", + ] + fail("\n".join(msglines), pytrace=False) + except Exception as exc: + msglines = [ + "Error evaluating %r condition" % mark.name, + " " + condition, + *traceback.format_exception_only(type(exc), exc), + ] + fail("\n".join(msglines), pytrace=False) + + # Boolean condition. + else: + try: + result = bool(condition) + except Exception as exc: + msglines = [ + "Error evaluating %r condition as a boolean" % mark.name, + *traceback.format_exception_only(type(exc), exc), + ] + fail("\n".join(msglines), pytrace=False) + + reason = mark.kwargs.get("reason", None) + if reason is None: + if isinstance(condition, str): + reason = "condition: " + condition else: - skip("unconditional skip") + # XXX better be checked at collection time + msg = ( + "Error evaluating %r: " % mark.name + + "you need to specify reason=STRING when using booleans as conditions." + ) + fail(msg, pytrace=False) - item._evalxfail = MarkEvaluator(item, "xfail") - check_xfail_no_run(item) + return result, reason -@hookimpl(hookwrapper=True) -def pytest_pyfunc_call(pyfuncitem): - check_xfail_no_run(pyfuncitem) - outcome = yield - passed = outcome.excinfo is None - if passed: - check_strict_xfail(pyfuncitem) +@attr.s(slots=True, frozen=True) +class Skip: + """The result of evaluate_skip_marks().""" + + reason = attr.ib(type=str) + + +def evaluate_skip_marks(item: Item) -> Optional[Skip]: + """Evaluate skip and skipif marks on item, returning Skip if triggered.""" + for mark in item.iter_markers(name="skipif"): + if "condition" not in mark.kwargs: + conditions = mark.args + else: + conditions = (mark.kwargs["condition"],) + + # Unconditional. + if not conditions: + reason = mark.kwargs.get("reason", "") + return Skip(reason) + + # If any of the conditions are true. + for condition in conditions: + result, reason = evaluate_condition(item, mark, condition) + if result: + return Skip(reason) + + for mark in item.iter_markers(name="skip"): + if "reason" in mark.kwargs: + reason = mark.kwargs["reason"] + elif mark.args: + reason = mark.args[0] + else: + reason = "unconditional skip" + return Skip(reason) + + return None + + +@attr.s(slots=True, frozen=True) +class Xfail: + """The result of evaluate_xfail_marks().""" + reason = attr.ib(type=str) + run = attr.ib(type=bool) + strict = attr.ib(type=bool) + raises = attr.ib(type=Optional[Tuple["Type[BaseException]", ...]]) -def check_xfail_no_run(item): - """check xfail(run=False)""" - if not item.config.option.runxfail: - evalxfail = item._evalxfail - if evalxfail.istrue(): - if not evalxfail.get("run", True): - xfail("[NOTRUN] " + evalxfail.getexplanation()) +def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: + """Evaluate xfail marks on item, returning Xfail if triggered.""" + for mark in item.iter_markers(name="xfail"): + run = mark.kwargs.get("run", True) + strict = mark.kwargs.get("strict", item.config.getini("xfail_strict")) + raises = mark.kwargs.get("raises", None) + if "condition" not in mark.kwargs: + conditions = mark.args + else: + conditions = (mark.kwargs["condition"],) + + # Unconditional. + if not conditions: + reason = mark.kwargs.get("reason", "") + return Xfail(reason, run, strict, raises) + + # If any of the conditions are true. + for condition in conditions: + result, reason = evaluate_condition(item, mark, condition) + if result: + return Xfail(reason, run, strict, raises) + + return None + + +# Whether skipped due to skip or skipif marks. +skipped_by_mark_key = StoreKey[bool]() +# Saves the xfail mark evaluation. Can be refreshed during call if None. +xfailed_key = StoreKey[Optional[Xfail]]() +unexpectedsuccess_key = StoreKey[str]() + + +@hookimpl(tryfirst=True) +def pytest_runtest_setup(item: Item) -> None: + skipped = evaluate_skip_marks(item) + item._store[skipped_by_mark_key] = skipped is not None + if skipped: + skip(skipped.reason) + + item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item) + if xfailed and not item.config.option.runxfail and not xfailed.run: + xfail("[NOTRUN] " + xfailed.reason) -def check_strict_xfail(pyfuncitem): - """check xfail(strict=True) for the given PASSING test""" - evalxfail = pyfuncitem._evalxfail - if evalxfail.istrue(): - strict_default = pyfuncitem.config.getini("xfail_strict") - is_strict_xfail = evalxfail.get("strict", strict_default) - if is_strict_xfail: - del pyfuncitem._evalxfail - explanation = evalxfail.getexplanation() - fail("[XPASS(strict)] " + explanation, pytrace=False) + +@hookimpl(hookwrapper=True) +def pytest_runtest_call(item: Item) -> Generator[None, None, None]: + xfailed = item._store.get(xfailed_key, None) + if xfailed is None: + item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item) + + if xfailed and not item.config.option.runxfail and not xfailed.run: + xfail("[NOTRUN] " + xfailed.reason) + + yield + + # The test run may have added an xfail mark dynamically. + xfailed = item._store.get(xfailed_key, None) + if xfailed is None: + item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item) @hookimpl(hookwrapper=True) -def pytest_runtest_makereport(item, call): +def pytest_runtest_makereport(item: Item, call: CallInfo[None]): outcome = yield rep = outcome.get_result() - evalxfail = getattr(item, "_evalxfail", None) - # unitttest special case, see setting of _unexpectedsuccess - if hasattr(item, "_unexpectedsuccess") and rep.when == "call": - from _pytest.compat import _is_unittest_unexpected_success_a_failure - - if item._unexpectedsuccess: - rep.longrepr = "Unexpected success: {}".format(item._unexpectedsuccess) + xfailed = item._store.get(xfailed_key, None) + # unittest special case, see setting of unexpectedsuccess_key + if unexpectedsuccess_key in item._store and rep.when == "call": + reason = item._store[unexpectedsuccess_key] + if reason: + rep.longrepr = "Unexpected success: {}".format(reason) else: rep.longrepr = "Unexpected success" - if _is_unittest_unexpected_success_a_failure(): - rep.outcome = "failed" - else: - rep.outcome = "passed" - rep.wasxfail = rep.longrepr + rep.outcome = "failed" elif item.config.option.runxfail: - pass # don't interefere - elif call.excinfo and call.excinfo.errisinstance(xfail.Exception): + pass # don't interfere + elif call.excinfo and isinstance(call.excinfo.value, xfail.Exception): + assert call.excinfo.value.msg is not None rep.wasxfail = "reason: " + call.excinfo.value.msg rep.outcome = "skipped" - elif evalxfail and not rep.skipped and evalxfail.wasvalid() and evalxfail.istrue(): + elif not rep.skipped and xfailed: if call.excinfo: - if evalxfail.invalidraise(call.excinfo.value): + raises = xfailed.raises + if raises is not None and not isinstance(call.excinfo.value, raises): rep.outcome = "failed" else: rep.outcome = "skipped" - rep.wasxfail = evalxfail.getexplanation() + rep.wasxfail = xfailed.reason elif call.when == "call": - strict_default = item.config.getini("xfail_strict") - is_strict_xfail = evalxfail.get("strict", strict_default) - explanation = evalxfail.getexplanation() - if is_strict_xfail: + if xfailed.strict: rep.outcome = "failed" - rep.longrepr = "[XPASS(strict)] {}".format(explanation) + rep.longrepr = "[XPASS(strict)] " + xfailed.reason else: rep.outcome = "passed" - rep.wasxfail = explanation - elif ( - getattr(item, "_skipped_by_mark", False) + rep.wasxfail = xfailed.reason + + if ( + item._store.get(skipped_by_mark_key, True) and rep.skipped and type(rep.longrepr) is tuple ): - # skipped by mark.skipif; change the location of the failure + # Skipped by mark.skipif; change the location of the failure # to point to the item definition, otherwise it will display - # the location of where the skip exception was raised within pytest - filename, line, reason = rep.longrepr - filename, line = item.location[:2] - rep.longrepr = filename, line, reason - - -# called by terminalreporter progress reporting + # the location of where the skip exception was raised within pytest. + _, _, reason = rep.longrepr + filename, line = item.reportinfo()[:2] + assert line is not None + rep.longrepr = str(filename), line + 1, reason -def pytest_report_teststatus(report): +def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: if hasattr(report, "wasxfail"): if report.skipped: return "xfailed", "x", "XFAIL" elif report.passed: return "xpassed", "X", "XPASS" + return None diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py index 88902595896..85cbe293151 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/stepwise.py @@ -1,8 +1,15 @@ -# -*- coding: utf-8 -*- +from typing import List +from typing import Optional + import pytest +from _pytest import nodes +from _pytest.config import Config +from _pytest.config.argparsing import Parser +from _pytest.main import Session +from _pytest.reports import TestReport -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("general") group.addoption( "--sw", @@ -20,25 +27,28 @@ def pytest_addoption(parser): @pytest.hookimpl -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") class StepwisePlugin: - def __init__(self, config): + def __init__(self, config: Config) -> None: self.config = config self.active = config.getvalue("stepwise") - self.session = None + self.session = None # type: Optional[Session] self.report_status = "" if self.active: + assert config.cache is not None self.lastfailed = config.cache.get("cache/stepwise", None) self.skip = config.getvalue("stepwise_skip") - def pytest_sessionstart(self, session): + def pytest_sessionstart(self, session: Session) -> None: self.session = session - def pytest_collection_modifyitems(self, session, config, items): + def pytest_collection_modifyitems( + self, session: Session, config: Config, items: List[nodes.Item] + ) -> None: if not self.active: return if not self.lastfailed: @@ -71,7 +81,7 @@ class StepwisePlugin: config.hook.pytest_deselected(items=already_passed) - def pytest_runtest_logreport(self, report): + def pytest_runtest_logreport(self, report: TestReport) -> None: if not self.active: return @@ -86,6 +96,7 @@ class StepwisePlugin: else: # Mark test as the last failing and interrupt the test session. self.lastfailed = report.nodeid + assert self.session is not None self.session.shouldstop = ( "Test failed, continuing from this test next run." ) @@ -97,11 +108,13 @@ class StepwisePlugin: if report.nodeid == self.lastfailed: self.lastfailed = None - def pytest_report_collectionfinish(self): + def pytest_report_collectionfinish(self) -> Optional[str]: if self.active and self.config.getoption("verbose") >= 0 and self.report_status: return "stepwise: %s" % self.report_status + return None - def pytest_sessionfinish(self, session): + def pytest_sessionfinish(self, session: Session) -> None: + assert self.config.cache is not None if self.active: self.config.cache.set("cache/stepwise", self.lastfailed) else: diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/store.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/store.py new file mode 100644 index 00000000000..fbf3c588f36 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/store.py @@ -0,0 +1,125 @@ +from typing import Any +from typing import cast +from typing import Dict +from typing import Generic +from typing import TypeVar +from typing import Union + + +__all__ = ["Store", "StoreKey"] + + +T = TypeVar("T") +D = TypeVar("D") + + +class StoreKey(Generic[T]): + """StoreKey is an object used as a key to a Store. + + A StoreKey is associated with the type T of the value of the key. + + A StoreKey is unique and cannot conflict with another key. + """ + + __slots__ = () + + +class Store: + """Store is a type-safe heterogenous mutable mapping that + allows keys and value types to be defined separately from + where it (the Store) is created. + + Usually you will be given an object which has a ``Store``: + + .. code-block:: python + + store: Store = some_object.store + + If a module wants to store data in this Store, it creates StoreKeys + for its keys (at the module level): + + .. code-block:: python + + some_str_key = StoreKey[str]() + some_bool_key = StoreKey[bool]() + + To store information: + + .. code-block:: python + + # Value type must match the key. + store[some_str_key] = "value" + store[some_bool_key] = True + + To retrieve the information: + + .. code-block:: python + + # The static type of some_str is str. + some_str = store[some_str_key] + # The static type of some_bool is bool. + some_bool = store[some_bool_key] + + Why use this? + ------------- + + Problem: module Internal defines an object. Module External, which + module Internal doesn't know about, receives the object and wants to + attach information to it, to be retrieved later given the object. + + Bad solution 1: Module External assigns private attributes directly on + the object. This doesn't work well because the type checker doesn't + know about these attributes and it complains about undefined attributes. + + Bad solution 2: module Internal adds a ``Dict[str, Any]`` attribute to + the object. Module External stores its data in private keys of this dict. + This doesn't work well because retrieved values are untyped. + + Good solution: module Internal adds a ``Store`` to the object. Module + External mints StoreKeys for its own keys. Module External stores and + retrieves its data using these keys. + """ + + __slots__ = ("_store",) + + def __init__(self) -> None: + self._store = {} # type: Dict[StoreKey[Any], object] + + def __setitem__(self, key: StoreKey[T], value: T) -> None: + """Set a value for key.""" + self._store[key] = value + + def __getitem__(self, key: StoreKey[T]) -> T: + """Get the value for key. + + Raises ``KeyError`` if the key wasn't set before. + """ + return cast(T, self._store[key]) + + def get(self, key: StoreKey[T], default: D) -> Union[T, D]: + """Get the value for key, or return default if the key wasn't set + before.""" + try: + return self[key] + except KeyError: + return default + + def setdefault(self, key: StoreKey[T], default: T) -> T: + """Return the value of key if already set, otherwise set the value + of key to default and return default.""" + try: + return self[key] + except KeyError: + self[key] = default + return default + + def __delitem__(self, key: StoreKey[T]) -> None: + """Delete the value for key. + + Raises ``KeyError`` if the key wasn't set before. + """ + del self._store[key] + + def __contains__(self, key: StoreKey[T]) -> bool: + """Return whether key was set.""" + return key in self._store diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py index 4418338c656..34933ad2185 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/terminal.py @@ -1,46 +1,91 @@ -# -*- coding: utf-8 -*- -""" terminal reporting of the full testing process. +"""Terminal reporting of the full testing process. This is a good source for looking at the various reporting hooks. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import argparse -import collections +import datetime +import inspect import platform import sys -import time +import warnings from functools import partial +from typing import Any +from typing import Callable +from typing import Dict +from typing import Generator +from typing import List +from typing import Mapping +from typing import Optional +from typing import Sequence +from typing import Set +from typing import TextIO +from typing import Tuple +from typing import Union import attr import pluggy import py -import six -from more_itertools import collapse import pytest from _pytest import nodes -from _pytest.main import EXIT_INTERRUPTED -from _pytest.main import EXIT_NOTESTSCOLLECTED -from _pytest.main import EXIT_OK -from _pytest.main import EXIT_TESTSFAILED -from _pytest.main import EXIT_USAGEERROR +from _pytest import timing +from _pytest._code import ExceptionInfo +from _pytest._code.code import ExceptionRepr +from _pytest._io.wcwidth import wcswidth +from _pytest.compat import final +from _pytest.compat import order_preserving_dict +from _pytest.compat import TYPE_CHECKING +from _pytest.config import _PluggyPlugin +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.config.argparsing import Parser +from _pytest.nodes import Item +from _pytest.nodes import Node +from _pytest.pathlib import absolutepath +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import Path +from _pytest.reports import BaseReport +from _pytest.reports import CollectReport +from _pytest.reports import TestReport + +if TYPE_CHECKING: + from typing_extensions import Literal + + from _pytest.main import Session + REPORT_COLLECTING_RESOLUTION = 0.5 +KNOWN_TYPES = ( + "failed", + "passed", + "skipped", + "deselected", + "xfailed", + "xpassed", + "warnings", + "error", +) + +_REPORTCHARS_DEFAULT = "fE" + class MoreQuietAction(argparse.Action): - """ - a modified copy of the argparse count action which counts down and updates - the legacy quiet attribute at the same time + """A modified copy of the argparse count action which counts down and updates + the legacy quiet attribute at the same time. - used to unify verbosity handling + Used to unify verbosity handling. """ - def __init__(self, option_strings, dest, default=None, required=False, help=None): - super(MoreQuietAction, self).__init__( + def __init__( + self, + option_strings: Sequence[str], + dest: str, + default: object = None, + required: bool = False, + help: Optional[str] = None, + ) -> None: + super().__init__( option_strings=option_strings, dest=dest, nargs=0, @@ -49,14 +94,20 @@ class MoreQuietAction(argparse.Action): help=help, ) - def __call__(self, parser, namespace, values, option_string=None): + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[object], None], + option_string: Optional[str] = None, + ) -> None: new_count = getattr(namespace, self.dest, 0) - 1 setattr(namespace, self.dest, new_count) # todo Deprecate config.quiet namespace.quiet = getattr(namespace, "quiet", 0) + 1 -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "reporting", after="general") group._addoption( "-v", @@ -65,7 +116,21 @@ def pytest_addoption(parser): default=0, dest="verbose", help="increase verbosity.", - ), + ) + group._addoption( + "--no-header", + action="store_true", + default=False, + dest="no_header", + help="disable header", + ) + group._addoption( + "--no-summary", + action="store_true", + default=False, + dest="no_summary", + help="disable summary", + ) group._addoption( "-q", "--quiet", @@ -73,21 +138,25 @@ def pytest_addoption(parser): default=0, dest="verbose", help="decrease verbosity.", - ), + ) group._addoption( - "--verbosity", dest="verbose", type=int, default=0, help="set verbosity" + "--verbosity", + dest="verbose", + type=int, + default=0, + help="set verbosity. Default is 0.", ) group._addoption( "-r", action="store", dest="reportchars", - default="", + default=_REPORTCHARS_DEFAULT, metavar="chars", help="show extra test summary info as specified by chars: (f)ailed, " "(E)rror, (s)kipped, (x)failed, (X)passed, " "(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. " - "Warnings are displayed at all times except when " - "--disable-warnings is set.", + "(w)arnings are enabled by default (see --disable-warnings), " + "'N' can be used to reset the list. (default: 'fE').", ) group._addoption( "--disable-warnings", @@ -139,6 +208,12 @@ def pytest_addoption(parser): choices=["yes", "no", "auto"], help="color terminal output (yes/no/auto).", ) + group._addoption( + "--code-highlight", + default="yes", + choices=["yes", "no"], + help="Whether code should be highlighted (only if --color is also enabled)", + ) parser.addini( "console_output_style", @@ -147,7 +222,7 @@ def pytest_addoption(parser): ) -def pytest_configure(config): +def pytest_configure(config: Config) -> None: reporter = TerminalReporter(config, sys.stdout) config.pluginmanager.register(reporter, "terminalreporter") if config.option.debug or config.option.traceconfig: @@ -159,37 +234,40 @@ def pytest_configure(config): config.trace.root.setprocessor("pytest:config", mywriter) -def getreportopt(config): +def getreportopt(config: Config) -> str: + reportchars = config.option.reportchars # type: str + + old_aliases = {"F", "S"} reportopts = "" - reportchars = config.option.reportchars - if not config.option.disable_warnings and "w" not in reportchars: - reportchars += "w" - elif config.option.disable_warnings and "w" in reportchars: - reportchars = reportchars.replace("w", "") - aliases = {"F", "S"} for char in reportchars: - # handle old aliases - if char in aliases: + if char in old_aliases: char = char.lower() if char == "a": - reportopts = "sxXwEf" + reportopts = "sxXEf" elif char == "A": - reportopts = "PpsxXwEf" - break + reportopts = "PpsxXEf" + elif char == "N": + reportopts = "" elif char not in reportopts: reportopts += char + + if not config.option.disable_warnings and "w" not in reportopts: + reportopts = "w" + reportopts + elif config.option.disable_warnings and "w" in reportopts: + reportopts = reportopts.replace("w", "") + return reportopts @pytest.hookimpl(trylast=True) # after _pytest.runner -def pytest_report_teststatus(report): +def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: letter = "F" if report.passed: letter = "." elif report.skipped: letter = "s" - outcome = report.outcome + outcome = report.outcome # type: str if report.when in ("collect", "setup", "teardown") and outcome == "failed": outcome = "error" letter = "E" @@ -198,118 +276,132 @@ def pytest_report_teststatus(report): @attr.s -class WarningReport(object): - """ - Simple structure to hold warnings information captured by ``pytest_warning_captured``. +class WarningReport: + """Simple structure to hold warnings information captured by ``pytest_warning_recorded``. - :ivar str message: user friendly message about the warning - :ivar str|None nodeid: node id that generated the warning (see ``get_location``). + :ivar str message: + User friendly message about the warning. + :ivar str|None nodeid: + nodeid that generated the warning (see ``get_location``). :ivar tuple|py.path.local fslocation: - file system location of the source of the warning (see ``get_location``). + File system location of the source of the warning (see ``get_location``). """ - message = attr.ib() - nodeid = attr.ib(default=None) - fslocation = attr.ib(default=None) + message = attr.ib(type=str) + nodeid = attr.ib(type=Optional[str], default=None) + fslocation = attr.ib( + type=Optional[Union[Tuple[str, int], py.path.local]], default=None + ) count_towards_summary = True - def get_location(self, config): - """ - Returns the more user-friendly information about the location - of a warning, or None. - """ + def get_location(self, config: Config) -> Optional[str]: + """Return the more user-friendly information about the location of a warning, or None.""" if self.nodeid: return self.nodeid if self.fslocation: if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2: filename, linenum = self.fslocation[:2] - relpath = py.path.local(filename).relto(config.invocation_dir) - if not relpath: - relpath = str(filename) - return "%s:%s" % (relpath, linenum) + relpath = bestrelpath( + config.invocation_params.dir, absolutepath(filename) + ) + return "{}:{}".format(relpath, linenum) else: return str(self.fslocation) return None -class TerminalReporter(object): - def __init__(self, config, file=None): +@final +class TerminalReporter: + def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: import _pytest.config self.config = config self._numcollected = 0 - self._session = None - self._showfspath = None + self._session = None # type: Optional[Session] + self._showfspath = None # type: Optional[bool] - self.stats = {} + self.stats = {} # type: Dict[str, List[Any]] + self._main_color = None # type: Optional[str] + self._known_types = None # type: Optional[List[str]] self.startdir = config.invocation_dir + self.startpath = config.invocation_params.dir if file is None: file = sys.stdout self._tw = _pytest.config.create_terminal_writer(config, file) - # self.writer will be deprecated in pytest-3.4 - self.writer = self._tw self._screen_width = self._tw.fullwidth - self.currentfspath = None + self.currentfspath = None # type: Union[None, Path, str, int] self.reportchars = getreportopt(config) self.hasmarkup = self._tw.hasmarkup self.isatty = file.isatty() - self._progress_nodeids_reported = set() + self._progress_nodeids_reported = set() # type: Set[str] self._show_progress_info = self._determine_show_progress_info() - self._collect_report_last_write = None + self._collect_report_last_write = None # type: Optional[float] + self._already_displayed_warnings = None # type: Optional[int] + self._keyboardinterrupt_memo = None # type: Optional[ExceptionRepr] - def _determine_show_progress_info(self): - """Return True if we should display progress information based on the current config""" + def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]": + """Return whether we should display progress information based on the current config.""" # do not show progress if we are not capturing output (#3038) if self.config.getoption("capture", "no") == "no": return False # do not show progress if we are showing fixture setup/teardown if self.config.getoption("setupshow", False): return False - cfg = self.config.getini("console_output_style") - if cfg in ("progress", "count"): - return cfg - return False + cfg = self.config.getini("console_output_style") # type: str + if cfg == "progress": + return "progress" + elif cfg == "count": + return "count" + else: + return False @property - def verbosity(self): - return self.config.option.verbose + def verbosity(self) -> int: + verbosity = self.config.option.verbose # type: int + return verbosity @property - def showheader(self): + def showheader(self) -> bool: return self.verbosity >= 0 @property - def showfspath(self): + def no_header(self) -> bool: + return bool(self.config.option.no_header) + + @property + def no_summary(self) -> bool: + return bool(self.config.option.no_summary) + + @property + def showfspath(self) -> bool: if self._showfspath is None: return self.verbosity >= 0 return self._showfspath @showfspath.setter - def showfspath(self, value): + def showfspath(self, value: Optional[bool]) -> None: self._showfspath = value @property - def showlongtestinfo(self): + def showlongtestinfo(self) -> bool: return self.verbosity > 0 - def hasopt(self, char): + def hasopt(self, char: str) -> bool: char = {"xfailed": "x", "skipped": "s"}.get(char, char) return char in self.reportchars - def write_fspath_result(self, nodeid, res, **markup): - fspath = self.config.rootdir.join(nodeid.split("::")[0]) - # NOTE: explicitly check for None to work around py bug, and for less - # overhead in general (https://github.com/pytest-dev/py/pull/207). + def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None: + fspath = self.config.rootpath / nodeid.split("::")[0] if self.currentfspath is None or fspath != self.currentfspath: if self.currentfspath is not None and self._show_progress_info: self._write_progress_information_filling_space() self.currentfspath = fspath - fspath = self.startdir.bestrelpath(fspath) + relfspath = bestrelpath(self.startpath, fspath) self._tw.line() - self._tw.write(fspath + " ") - self._tw.write(res, **markup) + self._tw.write(relfspath + " ") + self._tw.write(res, flush=True, **markup) - def write_ensure_prefix(self, prefix, extra="", **kwargs): + def write_ensure_prefix(self, prefix: str, extra: str = "", **kwargs) -> None: if self.currentfspath != prefix: self._tw.line() self.currentfspath = prefix @@ -318,25 +410,28 @@ class TerminalReporter(object): self._tw.write(extra, **kwargs) self.currentfspath = -2 - def ensure_newline(self): + def ensure_newline(self) -> None: if self.currentfspath: self._tw.line() self.currentfspath = None - def write(self, content, **markup): - self._tw.write(content, **markup) + def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: + self._tw.write(content, flush=flush, **markup) - def write_line(self, line, **markup): - if not isinstance(line, six.text_type): - line = six.text_type(line, errors="replace") + def flush(self) -> None: + self._tw.flush() + + def write_line(self, line: Union[str, bytes], **markup: bool) -> None: + if not isinstance(line, str): + line = str(line, errors="replace") self.ensure_newline() self._tw.line(line, **markup) - def rewrite(self, line, **markup): - """ - Rewinds the terminal cursor to the beginning and writes the given line. + def rewrite(self, line: str, **markup: bool) -> None: + """Rewinds the terminal cursor to the beginning and writes the given line. - :kwarg erase: if True, will also add spaces until the full terminal width to ensure + :param erase: + If True, will also add spaces until the full terminal width to ensure previous lines are properly erased. The rest of the keyword arguments are markup instructions. @@ -350,68 +445,84 @@ class TerminalReporter(object): line = str(line) self._tw.write("\r" + line + fill, **markup) - def write_sep(self, sep, title=None, **markup): + def write_sep( + self, + sep: str, + title: Optional[str] = None, + fullwidth: Optional[int] = None, + **markup: bool + ) -> None: self.ensure_newline() - self._tw.sep(sep, title, **markup) + self._tw.sep(sep, title, fullwidth, **markup) - def section(self, title, sep="=", **kw): + def section(self, title: str, sep: str = "=", **kw: bool) -> None: self._tw.sep(sep, title, **kw) - def line(self, msg, **kw): + def line(self, msg: str, **kw: bool) -> None: self._tw.line(msg, **kw) - def pytest_internalerror(self, excrepr): - for line in six.text_type(excrepr).split("\n"): + def _add_stats(self, category: str, items: Sequence[Any]) -> None: + set_main_color = category not in self.stats + self.stats.setdefault(category, []).extend(items) + if set_main_color: + self._set_main_color() + + def pytest_internalerror(self, excrepr: ExceptionRepr) -> bool: + for line in str(excrepr).split("\n"): self.write_line("INTERNALERROR> " + line) - return 1 + return True - def pytest_warning_captured(self, warning_message, item): - # from _pytest.nodes import get_fslocation_from_item + def pytest_warning_recorded( + self, warning_message: warnings.WarningMessage, nodeid: str, + ) -> None: from _pytest.warnings import warning_record_to_str - warnings = self.stats.setdefault("warnings", []) fslocation = warning_message.filename, warning_message.lineno message = warning_record_to_str(warning_message) - nodeid = item.nodeid if item is not None else "" warning_report = WarningReport( fslocation=fslocation, message=message, nodeid=nodeid ) - warnings.append(warning_report) + self._add_stats("warnings", [warning_report]) - def pytest_plugin_registered(self, plugin): + def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None: if self.config.option.traceconfig: - msg = "PLUGIN registered: %s" % (plugin,) - # XXX this event may happen during setup/teardown time + msg = "PLUGIN registered: {}".format(plugin) + # XXX This event may happen during setup/teardown time # which unfortunately captures our output here - # which garbles our output if we use self.write_line + # which garbles our output if we use self.write_line. self.write_line(msg) - def pytest_deselected(self, items): - self.stats.setdefault("deselected", []).extend(items) + def pytest_deselected(self, items: Sequence[Item]) -> None: + self._add_stats("deselected", items) - def pytest_runtest_logstart(self, nodeid, location): - # ensure that the path is printed before the - # 1st test of a module starts running + def pytest_runtest_logstart( + self, nodeid: str, location: Tuple[str, Optional[int], str] + ) -> None: + # Ensure that the path is printed before the + # 1st test of a module starts running. if self.showlongtestinfo: line = self._locationline(nodeid, *location) self.write_ensure_prefix(line, "") + self.flush() elif self.showfspath: - fsid = nodeid.split("::")[0] - self.write_fspath_result(fsid, "") + self.write_fspath_result(nodeid, "") + self.flush() - def pytest_runtest_logreport(self, report): + def pytest_runtest_logreport(self, report: TestReport) -> None: self._tests_ran = True rep = report - res = self.config.hook.pytest_report_teststatus(report=rep, config=self.config) + res = self.config.hook.pytest_report_teststatus( + report=rep, config=self.config + ) # type: Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]] category, letter, word = res - if isinstance(word, tuple): - word, markup = word - else: + if not isinstance(word, tuple): markup = None - self.stats.setdefault(category, []).append(rep) + else: + word, markup = word + self._add_stats(category, [rep]) if not letter and not word: - # probably passed setup/teardown + # Probably passed setup/teardown. return running_xdist = hasattr(rep, "node") if markup is None: @@ -427,10 +538,7 @@ class TerminalReporter(object): else: markup = {} if self.verbosity <= 0: - if not running_xdist and self.showfspath: - self.write_fspath_result(rep.nodeid, letter, **markup) - else: - self._tw.write(letter, **markup) + self._tw.write(letter, **markup) else: self._progress_nodeids_reported.add(rep.nodeid) line = self._locationline(rep.nodeid, *rep.location) @@ -450,8 +558,15 @@ class TerminalReporter(object): self._tw.write(word, **markup) self._tw.write(" " + line) self.currentfspath = -2 + self.flush() + + @property + def _is_last_item(self) -> bool: + assert self._session is not None + return len(self._progress_nodeids_reported) == self._session.testscollected - def pytest_runtest_logfinish(self, nodeid): + def pytest_runtest_logfinish(self, nodeid: str) -> None: + assert self._session if self.verbosity <= 0 and self._show_progress_info: if self._show_progress_info == "count": num_tests = self._session.testscollected @@ -460,19 +575,19 @@ class TerminalReporter(object): progress_length = len(" [100%]") self._progress_nodeids_reported.add(nodeid) - is_last_item = ( - len(self._progress_nodeids_reported) == self._session.testscollected - ) - if is_last_item: + + if self._is_last_item: self._write_progress_information_filling_space() else: + main_color, _ = self._get_main_color() w = self._width_of_current_line past_edge = w + progress_length + 1 >= self._screen_width if past_edge: msg = self._get_progress_information_message() - self._tw.write(msg + "\n", cyan=True) + self._tw.write(msg + "\n", **{main_color: True}) - def _get_progress_information_message(self): + def _get_progress_information_message(self) -> str: + assert self._session collected = self._session.testscollected if self._show_progress_info == "count": if collected: @@ -483,50 +598,48 @@ class TerminalReporter(object): return " [ {} / {} ]".format(collected, collected) else: if collected: - progress = len(self._progress_nodeids_reported) * 100 // collected - return " [{:3d}%]".format(progress) + return " [{:3d}%]".format( + len(self._progress_nodeids_reported) * 100 // collected + ) return " [100%]" - def _write_progress_information_filling_space(self): + def _write_progress_information_filling_space(self) -> None: + color, _ = self._get_main_color() msg = self._get_progress_information_message() w = self._width_of_current_line fill = self._tw.fullwidth - w - 1 - self.write(msg.rjust(fill), cyan=True) + self.write(msg.rjust(fill), flush=True, **{color: True}) @property - def _width_of_current_line(self): - """Return the width of current line, using the superior implementation of py-1.6 when available""" - try: - return self._tw.width_of_current_line - except AttributeError: - # py < 1.6.0 - return self._tw.chars_on_current_line + def _width_of_current_line(self) -> int: + """Return the width of the current line.""" + return self._tw.width_of_current_line - def pytest_collection(self): + def pytest_collection(self) -> None: if self.isatty: if self.config.option.verbose >= 0: - self.write("collecting ... ", bold=True) - self._collect_report_last_write = time.time() + self.write("collecting ... ", flush=True, bold=True) + self._collect_report_last_write = timing.time() elif self.config.option.verbose >= 1: - self.write("collecting ... ", bold=True) + self.write("collecting ... ", flush=True, bold=True) - def pytest_collectreport(self, report): + def pytest_collectreport(self, report: CollectReport) -> None: if report.failed: - self.stats.setdefault("error", []).append(report) + self._add_stats("error", [report]) elif report.skipped: - self.stats.setdefault("skipped", []).append(report) + self._add_stats("skipped", [report]) items = [x for x in report.result if isinstance(x, pytest.Item)] self._numcollected += len(items) if self.isatty: self.report_collect() - def report_collect(self, final=False): + def report_collect(self, final: bool = False) -> None: if self.config.option.verbose < 0: return if not final: # Only write "collecting" report every 0.5s. - t = time.time() + t = timing.time() if ( self._collect_report_last_write is not None and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION @@ -546,7 +659,7 @@ class TerminalReporter(object): str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s") ) if errors: - line += " / %d errors" % errors + line += " / %d error%s" % (errors, "s" if errors != 1 else "") if deselected: line += " / %d deselected" % deselected if skipped: @@ -561,49 +674,54 @@ class TerminalReporter(object): self.write_line(line) @pytest.hookimpl(trylast=True) - def pytest_sessionstart(self, session): + def pytest_sessionstart(self, session: "Session") -> None: self._session = session - self._sessionstarttime = time.time() + self._sessionstarttime = timing.time() if not self.showheader: return self.write_sep("=", "test session starts", bold=True) verinfo = platform.python_version() - msg = "platform %s -- Python %s" % (sys.platform, verinfo) - if hasattr(sys, "pypy_version_info"): - verinfo = ".".join(map(str, sys.pypy_version_info[:3])) - msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3]) - msg += ", pytest-%s, py-%s, pluggy-%s" % ( - pytest.__version__, - py.__version__, - pluggy.__version__, - ) - if ( - self.verbosity > 0 - or self.config.option.debug - or getattr(self.config.option, "pastebin", None) - ): - msg += " -- " + str(sys.executable) - self.write_line(msg) - lines = self.config.hook.pytest_report_header( - config=self.config, startdir=self.startdir - ) - self._write_report_lines_from_hooks(lines) - - def _write_report_lines_from_hooks(self, lines): - lines.reverse() - for line in collapse(lines): - self.write_line(line) + if not self.no_header: + msg = "platform {} -- Python {}".format(sys.platform, verinfo) + pypy_version_info = getattr(sys, "pypy_version_info", None) + if pypy_version_info: + verinfo = ".".join(map(str, pypy_version_info[:3])) + msg += "[pypy-{}-{}]".format(verinfo, pypy_version_info[3]) + msg += ", pytest-{}, py-{}, pluggy-{}".format( + pytest.__version__, py.__version__, pluggy.__version__ + ) + if ( + self.verbosity > 0 + or self.config.option.debug + or getattr(self.config.option, "pastebin", None) + ): + msg += " -- " + str(sys.executable) + self.write_line(msg) + lines = self.config.hook.pytest_report_header( + config=self.config, startdir=self.startdir + ) + self._write_report_lines_from_hooks(lines) + + def _write_report_lines_from_hooks( + self, lines: Sequence[Union[str, Sequence[str]]] + ) -> None: + for line_or_lines in reversed(lines): + if isinstance(line_or_lines, str): + self.write_line(line_or_lines) + else: + for line in line_or_lines: + self.write_line(line) - def pytest_report_header(self, config): - line = "rootdir: %s" % config.rootdir + def pytest_report_header(self, config: Config) -> List[str]: + line = "rootdir: %s" % config.rootpath - if config.inifile: - line += ", inifile: " + config.rootdir.bestrelpath(config.inifile) + if config.inipath: + line += ", configfile: " + bestrelpath(config.rootpath, config.inipath) - testpaths = config.getini("testpaths") + testpaths = config.getini("testpaths") # type: List[str] if testpaths and config.args == testpaths: - rel_paths = [config.rootdir.bestrelpath(x) for x in testpaths] - line += ", testpaths: {}".format(", ".join(rel_paths)) + line += ", testpaths: {}".format(", ".join(testpaths)) + result = [line] plugininfo = config.pluginmanager.list_plugin_distinfo() @@ -611,30 +729,33 @@ class TerminalReporter(object): result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo))) return result - def pytest_collection_finish(self, session): + def pytest_collection_finish(self, session: "Session") -> None: self.report_collect(True) - if self.config.getoption("collectonly"): - self._printcollecteditems(session.items) - lines = self.config.hook.pytest_report_collectionfinish( config=self.config, startdir=self.startdir, items=session.items ) self._write_report_lines_from_hooks(lines) if self.config.getoption("collectonly"): - if self.stats.get("failed"): + if session.items: + if self.config.option.verbose > -1: + self._tw.line("") + self._printcollecteditems(session.items) + + failed = self.stats.get("failed") + if failed: self._tw.sep("!", "collection failures") - for rep in self.stats.get("failed"): + for rep in failed: rep.toterminal(self._tw) - def _printcollecteditems(self, items): - # to print out items and their parent collectors + def _printcollecteditems(self, items: Sequence[Item]) -> None: + # To print out items and their parent collectors # we take care to leave out Instances aka () - # because later versions are going to get rid of them anyway + # because later versions are going to get rid of them anyway. if self.config.option.verbose < 0: if self.config.option.verbose < -1: - counts = {} + counts = {} # type: Dict[str, int] for item in items: name = item.nodeid.split("::", 1)[0] counts[name] = counts.get(name, 0) + 1 @@ -644,7 +765,7 @@ class TerminalReporter(object): for item in items: self._tw.line(item.nodeid) return - stack = [] + stack = [] # type: List[Node] indent = "" for item in items: needed_collectors = item.listchain()[1:] # strip root node @@ -657,35 +778,43 @@ class TerminalReporter(object): if col.name == "()": # Skip Instances. continue indent = (len(stack) - 1) * " " - self._tw.line("%s%s" % (indent, col)) + self._tw.line("{}{}".format(indent, col)) if self.config.option.verbose >= 1: - if hasattr(col, "_obj") and col._obj.__doc__: - for line in col._obj.__doc__.strip().splitlines(): - self._tw.line("%s%s" % (indent + " ", line.strip())) + obj = getattr(col, "obj", None) + doc = inspect.getdoc(obj) if obj else None + if doc: + for line in doc.splitlines(): + self._tw.line("{}{}".format(indent + " ", line)) @pytest.hookimpl(hookwrapper=True) - def pytest_sessionfinish(self, exitstatus): + def pytest_sessionfinish( + self, session: "Session", exitstatus: Union[int, ExitCode] + ): outcome = yield outcome.get_result() self._tw.line("") summary_exit_codes = ( - EXIT_OK, - EXIT_TESTSFAILED, - EXIT_INTERRUPTED, - EXIT_USAGEERROR, - EXIT_NOTESTSCOLLECTED, + ExitCode.OK, + ExitCode.TESTS_FAILED, + ExitCode.INTERRUPTED, + ExitCode.USAGE_ERROR, + ExitCode.NO_TESTS_COLLECTED, ) - if exitstatus in summary_exit_codes: + if exitstatus in summary_exit_codes and not self.no_summary: self.config.hook.pytest_terminal_summary( terminalreporter=self, exitstatus=exitstatus, config=self.config ) - if exitstatus == EXIT_INTERRUPTED: + if session.shouldfail: + self.write_sep("!", str(session.shouldfail), red=True) + if exitstatus == ExitCode.INTERRUPTED: self._report_keyboardinterrupt() - del self._keyboardinterrupt_memo + self._keyboardinterrupt_memo = None + elif session.shouldstop: + self.write_sep("!", str(session.shouldstop), red=True) self.summary_stats() @pytest.hookimpl(hookwrapper=True) - def pytest_terminal_summary(self): + def pytest_terminal_summary(self) -> Generator[None, None, None]: self.summary_errors() self.summary_failures() self.summary_warnings() @@ -695,15 +824,17 @@ class TerminalReporter(object): # Display any extra warnings from teardown here (if any). self.summary_warnings() - def pytest_keyboard_interrupt(self, excinfo): + def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None: self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) - def pytest_unconfigure(self): - if hasattr(self, "_keyboardinterrupt_memo"): + def pytest_unconfigure(self) -> None: + if self._keyboardinterrupt_memo is not None: self._report_keyboardinterrupt() - def _report_keyboardinterrupt(self): + def _report_keyboardinterrupt(self) -> None: excrepr = self._keyboardinterrupt_memo + assert excrepr is not None + assert excrepr.reprcrash is not None msg = excrepr.reprcrash.message self.write_sep("!", msg) if "KeyboardInterrupt" in msg: @@ -712,7 +843,7 @@ class TerminalReporter(object): else: excrepr.reprcrash.toterminal(self._tw) self._tw.line( - "(to show a full traceback on KeyboardInterrupt use --fulltrace)", + "(to show a full traceback on KeyboardInterrupt use --full-trace)", yellow=True, ) @@ -726,14 +857,14 @@ class TerminalReporter(object): line += "[".join(values) return line - # collect_fspath comes from testid which has a "/"-normalized path + # collect_fspath comes from testid which has a "/"-normalized path. if fspath: res = mkrel(nodeid) if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace( "\\", nodes.SEP ): - res += " <- " + self.startdir.bestrelpath(fspath) + res += " <- " + bestrelpath(self.startpath, fspath) else: res = "[location]" return res + " " @@ -754,22 +885,24 @@ class TerminalReporter(object): return "" # - # summaries for sessionfinish + # Summaries for sessionfinish. # - def getreports(self, name): + def getreports(self, name: str): values = [] for x in self.stats.get(name, []): if not hasattr(x, "_pdbshown"): values.append(x) return values - def summary_warnings(self): + def summary_warnings(self) -> None: if self.hasopt("w"): - all_warnings = self.stats.get("warnings") + all_warnings = self.stats.get( + "warnings" + ) # type: Optional[List[WarningReport]] if not all_warnings: return - final = hasattr(self, "_already_displayed_warnings") + final = self._already_displayed_warnings is not None if final: warning_reports = all_warnings[self._already_displayed_warnings :] else: @@ -778,20 +911,37 @@ class TerminalReporter(object): if not warning_reports: return - reports_grouped_by_message = collections.OrderedDict() + reports_grouped_by_message = ( + order_preserving_dict() + ) # type: Dict[str, List[WarningReport]] for wr in warning_reports: reports_grouped_by_message.setdefault(wr.message, []).append(wr) - title = "warnings summary (final)" if final else "warnings summary" - self.write_sep("=", title, yellow=True, bold=False) - for message, warning_reports in reports_grouped_by_message.items(): - has_any_location = False - for w in warning_reports: + def collapsed_location_report(reports: List[WarningReport]) -> str: + locations = [] + for w in reports: location = w.get_location(self.config) if location: - self._tw.line(str(location)) - has_any_location = True - if has_any_location: + locations.append(location) + + if len(locations) < 10: + return "\n".join(map(str, locations)) + + counts_by_filename = order_preserving_dict() # type: Dict[str, int] + for loc in locations: + key = str(loc).split("::", 1)[0] + counts_by_filename[key] = counts_by_filename.get(key, 0) + 1 + return "\n".join( + "{}: {} warning{}".format(k, v, "s" if v > 1 else "") + for k, v in counts_by_filename.items() + ) + + title = "warnings summary (final)" if final else "warnings summary" + self.write_sep("=", title, yellow=True, bold=False) + for message, message_reports in reports_grouped_by_message.items(): + maybe_location = collapsed_location_report(message_reports) + if maybe_location: + self._tw.line(maybe_location) lines = message.splitlines() indented = "\n".join(" " + x for x in lines) message = indented.rstrip() @@ -799,12 +949,12 @@ class TerminalReporter(object): message = message.rstrip() self._tw.line(message) self._tw.line() - self._tw.line("-- Docs: https://docs.pytest.org/en/latest/warnings.html") + self._tw.line("-- Docs: https://docs.pytest.org/en/stable/warnings.html") - def summary_passes(self): + def summary_passes(self) -> None: if self.config.option.tbstyle != "no": if self.hasopt("P"): - reports = self.getreports("passed") + reports = self.getreports("passed") # type: List[TestReport] if not reports: return self.write_sep("=", "PASSES") @@ -813,8 +963,21 @@ class TerminalReporter(object): msg = self._getfailureheadline(rep) self.write_sep("_", msg, green=True, bold=True) self._outrep_summary(rep) + self._handle_teardown_sections(rep.nodeid) - def print_teardown_sections(self, rep): + def _get_teardown_reports(self, nodeid: str) -> List[TestReport]: + reports = self.getreports("") + return [ + report + for report in reports + if report.when == "teardown" and report.nodeid == nodeid + ] + + def _handle_teardown_sections(self, nodeid: str) -> None: + for report in self._get_teardown_reports(nodeid): + self.print_teardown_sections(report) + + def print_teardown_sections(self, rep: TestReport) -> None: showcapture = self.config.option.showcapture if showcapture == "no": return @@ -827,9 +990,9 @@ class TerminalReporter(object): content = content[:-1] self._tw.line(content) - def summary_failures(self): + def summary_failures(self) -> None: if self.config.option.tbstyle != "no": - reports = self.getreports("failed") + reports = self.getreports("failed") # type: List[BaseReport] if not reports: return self.write_sep("=", "FAILURES") @@ -838,21 +1001,15 @@ class TerminalReporter(object): line = self._getcrashline(rep) self.write_line(line) else: - teardown_sections = {} - for report in self.getreports(""): - if report.when == "teardown": - teardown_sections.setdefault(report.nodeid, []).append(report) - for rep in reports: msg = self._getfailureheadline(rep) self.write_sep("_", msg, red=True, bold=True) self._outrep_summary(rep) - for report in teardown_sections.get(rep.nodeid, []): - self.print_teardown_sections(report) + self._handle_teardown_sections(rep.nodeid) - def summary_errors(self): + def summary_errors(self) -> None: if self.config.option.tbstyle != "no": - reports = self.getreports("error") + reports = self.getreports("error") # type: List[BaseReport] if not reports: return self.write_sep("=", "ERRORS") @@ -861,11 +1018,11 @@ class TerminalReporter(object): if rep.when == "collect": msg = "ERROR collecting " + msg else: - msg = "ERROR at %s of %s" % (rep.when, msg) + msg = "ERROR at {} of {}".format(rep.when, msg) self.write_sep("_", msg, red=True, bold=True) self._outrep_summary(rep) - def _outrep_summary(self, rep): + def _outrep_summary(self, rep: BaseReport) -> None: rep.toterminal(self._tw) showcapture = self.config.option.showcapture if showcapture == "no": @@ -878,52 +1035,78 @@ class TerminalReporter(object): content = content[:-1] self._tw.line(content) - def summary_stats(self): - session_duration = time.time() - self._sessionstarttime - (line, color) = build_summary_stats_line(self.stats) - msg = "%s in %.2f seconds" % (line, session_duration) - markup = {color: True, "bold": True} + def summary_stats(self) -> None: + if self.verbosity < -1: + return - if self.verbosity >= 0: - self.write_sep("=", msg, **markup) - if self.verbosity == -1: - self.write_line(msg, **markup) + session_duration = timing.time() - self._sessionstarttime + (parts, main_color) = self.build_summary_stats_line() + line_parts = [] + + display_sep = self.verbosity >= 0 + if display_sep: + fullwidth = self._tw.fullwidth + for text, markup in parts: + with_markup = self._tw.markup(text, **markup) + if display_sep: + fullwidth += len(with_markup) - len(text) + line_parts.append(with_markup) + msg = ", ".join(line_parts) + + main_markup = {main_color: True} + duration = " in {}".format(format_session_duration(session_duration)) + duration_with_markup = self._tw.markup(duration, **main_markup) + if display_sep: + fullwidth += len(duration_with_markup) - len(duration) + msg += duration_with_markup + + if display_sep: + markup_for_end_sep = self._tw.markup("", **main_markup) + if markup_for_end_sep.endswith("\x1b[0m"): + markup_for_end_sep = markup_for_end_sep[:-4] + fullwidth += len(markup_for_end_sep) + msg += markup_for_end_sep + + if display_sep: + self.write_sep("=", msg, fullwidth=fullwidth, **main_markup) + else: + self.write_line(msg, **main_markup) - def short_test_summary(self): + def short_test_summary(self) -> None: if not self.reportchars: return - def show_simple(stat, lines): + def show_simple(stat, lines: List[str]) -> None: failed = self.stats.get(stat, []) if not failed: return - termwidth = self.writer.fullwidth + termwidth = self._tw.fullwidth config = self.config for rep in failed: line = _get_line_with_reprcrash_message(config, rep, termwidth) lines.append(line) - def show_xfailed(lines): + def show_xfailed(lines: List[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: verbose_word = rep._get_verbose_word(self.config) pos = _get_pos(self.config, rep) - lines.append("%s %s" % (verbose_word, pos)) + lines.append("{} {}".format(verbose_word, pos)) reason = rep.wasxfail if reason: lines.append(" " + str(reason)) - def show_xpassed(lines): + def show_xpassed(lines: List[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: verbose_word = rep._get_verbose_word(self.config) pos = _get_pos(self.config, rep) reason = rep.wasxfail - lines.append("%s %s %s" % (verbose_word, pos, reason)) + lines.append("{} {} {}".format(verbose_word, pos, reason)) - def show_skipped(lines): - skipped = self.stats.get("skipped", []) - fskips = _folded_skips(skipped) if skipped else [] + def show_skipped(lines: List[str]) -> None: + skipped = self.stats.get("skipped", []) # type: List[CollectReport] + fskips = _folded_skips(self.startpath, skipped) if skipped else [] if not fskips: return verbose_word = skipped[0]._get_verbose_word(self.config) @@ -933,7 +1116,7 @@ class TerminalReporter(object): if lineno is not None: lines.append( "%s [%d] %s:%d: %s" - % (verbose_word, num, fspath, lineno + 1, reason) + % (verbose_word, num, fspath, lineno, reason) ) else: lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) @@ -945,9 +1128,9 @@ class TerminalReporter(object): "s": show_skipped, "p": partial(show_simple, "passed"), "E": partial(show_simple, "error"), - } + } # type: Mapping[str, Callable[[List[str]], None]] - lines = [] + lines = [] # type: List[str] for char in self.reportchars: action = REPORTCHAR_ACTIONS.get(char) if action: # skipping e.g. "P" (passed with output) here. @@ -958,20 +1141,67 @@ class TerminalReporter(object): for line in lines: self.write_line(line) + def _get_main_color(self) -> Tuple[str, List[str]]: + if self._main_color is None or self._known_types is None or self._is_last_item: + self._set_main_color() + assert self._main_color + assert self._known_types + return self._main_color, self._known_types + + def _determine_main_color(self, unknown_type_seen: bool) -> str: + stats = self.stats + if "failed" in stats or "error" in stats: + main_color = "red" + elif "warnings" in stats or "xpassed" in stats or unknown_type_seen: + main_color = "yellow" + elif "passed" in stats or not self._is_last_item: + main_color = "green" + else: + main_color = "yellow" + return main_color + + def _set_main_color(self) -> None: + unknown_types = [] # type: List[str] + for found_type in self.stats.keys(): + if found_type: # setup/teardown reports have an empty key, ignore them + if found_type not in KNOWN_TYPES and found_type not in unknown_types: + unknown_types.append(found_type) + self._known_types = list(KNOWN_TYPES) + unknown_types + self._main_color = self._determine_main_color(bool(unknown_types)) + + def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + main_color, known_types = self._get_main_color() + + parts = [] + for key in known_types: + reports = self.stats.get(key, None) + if reports: + count = sum( + 1 for rep in reports if getattr(rep, "count_towards_summary", True) + ) + color = _color_for_type.get(key, _color_for_type_default) + markup = {color: True, "bold": color == main_color} + parts.append(("%d %s" % _make_plural(count, key), markup)) + + if not parts: + parts = [("no tests ran", {_color_for_type_default: True})] + + return parts, main_color + -def _get_pos(config, rep): +def _get_pos(config: Config, rep: BaseReport): nodeid = config.cwd_relative_nodeid(rep.nodeid) return nodeid -def _get_line_with_reprcrash_message(config, rep, termwidth): +def _get_line_with_reprcrash_message( + config: Config, rep: BaseReport, termwidth: int +) -> str: """Get summary line for a report, trying to add reprcrash message.""" - from wcwidth import wcswidth - verbose_word = rep._get_verbose_word(config) pos = _get_pos(config, rep) - line = "%s %s" % (verbose_word, pos) + line = "{} {}".format(verbose_word, pos) len_line = wcswidth(line) ellipsis, len_ellipsis = "...", 3 if len_line > termwidth - len_ellipsis: @@ -979,7 +1209,8 @@ def _get_line_with_reprcrash_message(config, rep, termwidth): return line try: - msg = rep.longrepr.reprcrash.message + # Type ignored intentionally -- possible AttributeError expected. + msg = rep.longrepr.reprcrash.message # type: ignore[union-attr] except AttributeError: pass else: @@ -997,94 +1228,81 @@ def _get_line_with_reprcrash_message(config, rep, termwidth): msg = msg[:max_len_msg] while wcswidth(msg) > max_len_msg: msg = msg[:-1] - if six.PY2: - # on python 2 systems with narrow unicode compilation, trying to - # get a single character out of a multi-byte unicode character such as - # u'😄' will result in a High Surrogate (U+D83D) character, which is - # rendered as u'�'; in this case we just strip that character out as it - # serves no purpose being rendered - try: - surrogate = six.unichr(0xD83D) - msg = msg.rstrip(surrogate) - except ValueError: # pragma: no cover - # Jython cannot represent this lone surrogate at all (#5256): - # ValueError: unichr() arg is a lone surrogate in range - # (0xD800, 0xDFFF) (Jython UTF-16 encoding) - # ignore this case as it shouldn't appear in the string anyway - pass msg += ellipsis line += sep + msg return line -def _folded_skips(skipped): - d = {} +def _folded_skips( + startpath: Path, skipped: Sequence[CollectReport], +) -> List[Tuple[int, str, Optional[int], str]]: + d = {} # type: Dict[Tuple[str, Optional[int], str], List[CollectReport]] for event in skipped: - key = event.longrepr - assert len(key) == 3, (event, key) + assert event.longrepr is not None + assert isinstance(event.longrepr, tuple), (event, event.longrepr) + assert len(event.longrepr) == 3, (event, event.longrepr) + fspath, lineno, reason = event.longrepr + # For consistency, report all fspaths in relative form. + fspath = bestrelpath(startpath, Path(fspath)) keywords = getattr(event, "keywords", {}) - # folding reports with global pytestmark variable - # this is workaround, because for now we cannot identify the scope of a skip marker - # TODO: revisit after marks scope would be fixed + # Folding reports with global pytestmark variable. + # This is a workaround, because for now we cannot identify the scope of a skip marker + # TODO: Revisit after marks scope would be fixed. if ( event.when == "setup" and "skip" in keywords and "pytestmark" not in keywords ): - key = (key[0], None, key[2]) + key = (fspath, None, reason) # type: Tuple[str, Optional[int], str] + else: + key = (fspath, lineno, reason) d.setdefault(key, []).append(event) - values = [] + values = [] # type: List[Tuple[int, str, Optional[int], str]] for key, events in d.items(): - values.append((len(events),) + key) + values.append((len(events), *key)) return values -def build_summary_stats_line(stats): - known_types = ( - "failed passed skipped deselected xfailed xpassed warnings error".split() - ) - unknown_type_seen = False - for found_type in stats: - if found_type not in known_types: - if found_type: # setup/teardown reports have an empty key, ignore them - known_types.append(found_type) - unknown_type_seen = True - parts = [] - for key in known_types: - reports = stats.get(key, None) - if reports: - count = sum( - 1 for rep in reports if getattr(rep, "count_towards_summary", True) - ) - parts.append("%d %s" % (count, key)) +_color_for_type = { + "failed": "red", + "error": "red", + "warnings": "yellow", + "passed": "green", +} +_color_for_type_default = "yellow" - if parts: - line = ", ".join(parts) - else: - line = "no tests ran" - - if "failed" in stats or "error" in stats: - color = "red" - elif "warnings" in stats or unknown_type_seen: - color = "yellow" - elif "passed" in stats: - color = "green" - else: - color = "yellow" - return line, color +def _make_plural(count: int, noun: str) -> Tuple[int, str]: + # No need to pluralize words such as `failed` or `passed`. + if noun not in ["error", "warnings"]: + return count, noun + + # The `warnings` key is plural. To avoid API breakage, we keep it that way but + # set it to singular here so we can determine plurality in the same way as we do + # for `error`. + noun = noun.replace("warnings", "warning") + + return count, noun + "s" if count != 1 else noun -def _plugin_nameversions(plugininfo): - values = [] +def _plugin_nameversions(plugininfo) -> List[str]: + values = [] # type: List[str] for plugin, dist in plugininfo: - # gets us name and version! + # Gets us name and version! name = "{dist.project_name}-{dist.version}".format(dist=dist) - # questionable convenience, but it keeps things short + # Questionable convenience, but it keeps things short. if name.startswith("pytest-"): name = name[7:] - # we decided to print python package names - # they can have more than one plugin + # We decided to print python package names they can have more than one plugin. if name not in values: values.append(name) return values + + +def format_session_duration(seconds: float) -> str: + """Format the given seconds in a human readable manner to show in the final summary.""" + if seconds < 60: + return "{:.2f}s".format(seconds) + else: + dt = datetime.timedelta(seconds=int(seconds)) + return "{:.2f}s ({})".format(seconds, dt) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py new file mode 100644 index 00000000000..62442de7528 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/timing.py @@ -0,0 +1,12 @@ +"""Indirection for time functions. + +We intentionally grab some "time" functions internally to avoid tests mocking "time" to affect +pytest runtime information (issue #185). + +Fixture "mock_timinig" also interacts with this module for pytest's own tests. +""" +from time import perf_counter +from time import sleep +from time import time + +__all__ = ["perf_counter", "sleep", "time"] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py index a8a7037713c..eb8aa9f9104 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/tmpdir.py @@ -1,17 +1,11 @@ -# -*- coding: utf-8 -*- -""" support for providing temporary directories to test functions. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Support for providing temporary directories to test functions.""" import os import re import tempfile -import warnings +from typing import Optional import attr import py -import six import pytest from .pathlib import ensure_reset_dir @@ -19,37 +13,64 @@ from .pathlib import LOCK_TIMEOUT from .pathlib import make_numbered_dir from .pathlib import make_numbered_dir_with_cleanup from .pathlib import Path +from _pytest.compat import final +from _pytest.config import Config +from _pytest.fixtures import FixtureRequest from _pytest.monkeypatch import MonkeyPatch +@final @attr.s -class TempPathFactory(object): +class TempPathFactory: """Factory for temporary directories under the common base temp directory. - The base directory can be configured using the ``--basetemp`` option.""" + The base directory can be configured using the ``--basetemp`` option. + """ _given_basetemp = attr.ib( - # using os.path.abspath() to get absolute path instead of resolve() as it - # does not work the same in all platforms (see #4427) - # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012) + type=Optional[Path], + # Use os.path.abspath() to get absolute path instead of resolve() as it + # does not work the same in all platforms (see #4427). + # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012). + # Ignore type because of https://github.com/python/mypy/issues/6172. converter=attr.converters.optional( - lambda p: Path(os.path.abspath(six.text_type(p))) - ) + lambda p: Path(os.path.abspath(str(p))) # type: ignore + ), ) _trace = attr.ib() - _basetemp = attr.ib(default=None) + _basetemp = attr.ib(type=Optional[Path], default=None) @classmethod - def from_config(cls, config): - """ - :param config: a pytest configuration - """ + def from_config(cls, config: Config) -> "TempPathFactory": + """Create a factory according to pytest configuration.""" return cls( given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir") ) - def mktemp(self, basename, numbered=True): - """makes a temporary directory managed by the factory""" + def _ensure_relative_to_basetemp(self, basename: str) -> str: + basename = os.path.normpath(basename) + if (self.getbasetemp() / basename).resolve().parent != self.getbasetemp(): + raise ValueError( + "{} is not a normalized and relative path".format(basename) + ) + return basename + + def mktemp(self, basename: str, numbered: bool = True) -> Path: + """Create a new temporary directory managed by the factory. + + :param basename: + Directory base name, must be a relative path. + + :param numbered: + If ``True``, ensure the directory is unique by adding a numbered + suffix greater than any existing one: ``basename="foo-"`` and ``numbered=True`` + means that this function will create directories named ``"foo-0"``, + ``"foo-1"``, ``"foo-2"`` and so on. + + :returns: + The path to the new directory. + """ + basename = self._ensure_relative_to_basetemp(basename) if not numbered: p = self.getbasetemp().joinpath(basename) p.mkdir() @@ -58,8 +79,8 @@ class TempPathFactory(object): self._trace("mktemp", p) return p - def getbasetemp(self): - """ return base temporary directory. """ + def getbasetemp(self) -> Path: + """Return base temporary directory.""" if self._basetemp is not None: return self._basetemp @@ -84,44 +105,26 @@ class TempPathFactory(object): return t +@final @attr.s -class TempdirFactory(object): - """ - backward comptibility wrapper that implements - :class:``py.path.local`` for :class:``TempPathFactory`` - """ - - _tmppath_factory = attr.ib() +class TempdirFactory: + """Backward comptibility wrapper that implements :class:``py.path.local`` + for :class:``TempPathFactory``.""" - def ensuretemp(self, string, dir=1): - """ (deprecated) return temporary directory path with - the given string as the trailing part. It is usually - better to use the 'tmpdir' function argument which - provides an empty unique-per-test-invocation directory - and is guaranteed to be empty. - """ - # py.log._apiwarn(">1.1", "use tmpdir function argument") - from .deprecated import PYTEST_ENSURETEMP - - warnings.warn(PYTEST_ENSURETEMP, stacklevel=2) - return self.getbasetemp().ensure(string, dir=dir) + _tmppath_factory = attr.ib(type=TempPathFactory) - def mktemp(self, basename, numbered=True): - """Create a subdirectory of the base temporary directory and return it. - If ``numbered``, ensure the directory is unique by adding a number - prefix greater than any existing one. - """ + def mktemp(self, basename: str, numbered: bool = True) -> py.path.local: + """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve()) - def getbasetemp(self): - """backward compat wrapper for ``_tmppath_factory.getbasetemp``""" + def getbasetemp(self) -> py.path.local: + """Backward compat wrapper for ``_tmppath_factory.getbasetemp``.""" return py.path.local(self._tmppath_factory.getbasetemp().resolve()) -def get_user(): +def get_user() -> Optional[str]: """Return the current user name, or None if getuser() does not work - in the current environment (see #1010). - """ + in the current environment (see #1010).""" import getpass try: @@ -130,7 +133,7 @@ def get_user(): return None -def pytest_configure(config): +def pytest_configure(config: Config) -> None: """Create a TempdirFactory and attach it to the config object. This is to comply with existing plugins which expect the handler to be @@ -143,24 +146,23 @@ def pytest_configure(config): config._cleanup.append(mp.undo) mp.setattr(config, "_tmp_path_factory", tmppath_handler, raising=False) mp.setattr(config, "_tmpdirhandler", t, raising=False) - mp.setattr(pytest, "ensuretemp", t.ensuretemp, raising=False) @pytest.fixture(scope="session") -def tmpdir_factory(request): - """Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session. - """ - return request.config._tmpdirhandler +def tmpdir_factory(request: FixtureRequest) -> TempdirFactory: + """Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.""" + # Set dynamically by pytest_configure() above. + return request.config._tmpdirhandler # type: ignore @pytest.fixture(scope="session") -def tmp_path_factory(request): - """Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session. - """ - return request.config._tmp_path_factory +def tmp_path_factory(request: FixtureRequest) -> TempPathFactory: + """Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.""" + # Set dynamically by pytest_configure() above. + return request.config._tmp_path_factory # type: ignore -def _mk_tmp(request, factory): +def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path: name = request.node.name name = re.sub(r"[\W]", "_", name) MAXVAL = 30 @@ -169,12 +171,12 @@ def _mk_tmp(request, factory): @pytest.fixture -def tmpdir(tmp_path): - """Return a temporary directory path object - which is unique to each test function invocation, - created as a sub directory of the base temporary - directory. The returned object is a `py.path.local`_ - path object. +def tmpdir(tmp_path: Path) -> py.path.local: + """Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + The returned object is a `py.path.local`_ path object. .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html """ @@ -182,16 +184,16 @@ def tmpdir(tmp_path): @pytest.fixture -def tmp_path(request, tmp_path_factory): - """Return a temporary directory path object - which is unique to each test function invocation, - created as a sub directory of the base temporary - directory. The returned object is a :class:`pathlib.Path` - object. +def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path: + """Return a temporary directory path object which is unique to each test + function invocation, created as a sub directory of the base temporary + directory. + + The returned object is a :class:`pathlib.Path` object. .. note:: - in python < 3.6 this is a pathlib2.Path + In python < 3.6 this is a pathlib2.Path. """ return _mk_tmp(request, tmp_path_factory) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py index 3ff6f45d8db..09aa014c58a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/unittest.py @@ -1,47 +1,77 @@ -# -*- coding: utf-8 -*- -""" discovery and running of std-library "unittest" style tests. """ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Discover and run std-library "unittest" style tests.""" import sys import traceback +import types +from typing import Any +from typing import Callable +from typing import Generator +from typing import Iterable +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union import _pytest._code import pytest from _pytest.compat import getimfunc +from _pytest.compat import is_async_function +from _pytest.compat import TYPE_CHECKING from _pytest.config import hookimpl +from _pytest.fixtures import FixtureRequest +from _pytest.nodes import Collector +from _pytest.nodes import Item +from _pytest.outcomes import exit from _pytest.outcomes import fail from _pytest.outcomes import skip from _pytest.outcomes import xfail from _pytest.python import Class from _pytest.python import Function +from _pytest.python import PyCollector +from _pytest.runner import CallInfo +from _pytest.skipping import skipped_by_mark_key +from _pytest.skipping import unexpectedsuccess_key + +if TYPE_CHECKING: + import unittest + from typing import Type + + from _pytest.fixtures import _Scope + _SysExcInfoType = Union[ + Tuple[Type[BaseException], BaseException, types.TracebackType], + Tuple[None, None, None], + ] -def pytest_pycollect_makeitem(collector, name, obj): - # has unittest been imported and is obj a subclass of its TestCase? + +def pytest_pycollect_makeitem( + collector: PyCollector, name: str, obj: object +) -> Optional["UnitTestCase"]: + # Has unittest been imported and is obj a subclass of its TestCase? try: - if not issubclass(obj, sys.modules["unittest"].TestCase): - return + ut = sys.modules["unittest"] + # Type ignored because `ut` is an opaque module. + if not issubclass(obj, ut.TestCase): # type: ignore + return None except Exception: - return - # yes, so let's collect it - return UnitTestCase(name, parent=collector) + return None + # Yes, so let's collect it. + item = UnitTestCase.from_parent(collector, name=name, obj=obj) # type: UnitTestCase + return item class UnitTestCase(Class): - # marker for fixturemanger.getfixtureinfo() - # to declare that our children do not support funcargs + # Marker for fixturemanger.getfixtureinfo() + # to declare that our children do not support funcargs. nofuncargs = True - def collect(self): + def collect(self) -> Iterable[Union[Item, Collector]]: from unittest import TestLoader cls = self.obj if not getattr(cls, "__test__", True): return - skipped = getattr(cls, "__unittest_skip__", False) + skipped = _is_skipped(cls) if not skipped: self._inject_setup_teardown_fixtures(cls) self._inject_setup_class_fixture() @@ -54,41 +84,44 @@ class UnitTestCase(Class): if not getattr(x, "__test__", True): continue funcobj = getimfunc(x) - yield TestCaseFunction(name, parent=self, callobj=funcobj) + yield TestCaseFunction.from_parent(self, name=name, callobj=funcobj) foundsomething = True if not foundsomething: runtest = getattr(self.obj, "runTest", None) if runtest is not None: ut = sys.modules.get("twisted.trial.unittest", None) - if ut is None or runtest != ut.TestCase.runTest: - yield TestCaseFunction("runTest", parent=self) + # Type ignored because `ut` is an opaque module. + if ut is None or runtest != ut.TestCase.runTest: # type: ignore + yield TestCaseFunction.from_parent(self, name="runTest") - def _inject_setup_teardown_fixtures(self, cls): + def _inject_setup_teardown_fixtures(self, cls: type) -> None: """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding - teardown functions (#517)""" + teardown functions (#517).""" class_fixture = _make_xunit_fixture( cls, "setUpClass", "tearDownClass", scope="class", pass_self=False ) if class_fixture: - cls.__pytest_class_setup = class_fixture + cls.__pytest_class_setup = class_fixture # type: ignore[attr-defined] method_fixture = _make_xunit_fixture( cls, "setup_method", "teardown_method", scope="function", pass_self=True ) if method_fixture: - cls.__pytest_method_setup = method_fixture + cls.__pytest_method_setup = method_fixture # type: ignore[attr-defined] -def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self): +def _make_xunit_fixture( + obj: type, setup_name: str, teardown_name: str, scope: "_Scope", pass_self: bool +): setup = getattr(obj, setup_name, None) teardown = getattr(obj, teardown_name, None) if setup is None and teardown is None: return None @pytest.fixture(scope=scope, autouse=True) - def fixture(self, request): - if getattr(self, "__unittest_skip__", None): + def fixture(self, request: FixtureRequest) -> Generator[None, None, None]: + if _is_skipped(self): reason = self.__unittest_skip_why__ pytest.skip(reason) if setup is not None: @@ -108,43 +141,35 @@ def _make_xunit_fixture(obj, setup_name, teardown_name, scope, pass_self): class TestCaseFunction(Function): nofuncargs = True - _excinfo = None - _testcase = None - - def setup(self): - self._testcase = self.parent.obj(self.name) - self._fix_unittest_skip_decorator() + _excinfo = None # type: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] + _testcase = None # type: Optional[unittest.TestCase] + + def setup(self) -> None: + # A bound method to be called during teardown() if set (see 'runtest()'). + self._explicit_tearDown = None # type: Optional[Callable[[], None]] + assert self.parent is not None + self._testcase = self.parent.obj(self.name) # type: ignore[attr-defined] self._obj = getattr(self._testcase, self.name) if hasattr(self, "_request"): self._request._fillfixtures() - def _fix_unittest_skip_decorator(self): - """ - The @unittest.skip decorator calls functools.wraps(self._testcase) - The call to functools.wraps() fails unless self._testcase - has a __name__ attribute. This is usually automatically supplied - if the test is a function or method, but we need to add manually - here. - - See issue #1169 - """ - if sys.version_info[0] == 2: - setattr(self._testcase, "__name__", self.name) - - def teardown(self): + def teardown(self) -> None: + if self._explicit_tearDown is not None: + self._explicit_tearDown() + self._explicit_tearDown = None self._testcase = None self._obj = None - def startTest(self, testcase): + def startTest(self, testcase: "unittest.TestCase") -> None: pass - def _addexcinfo(self, rawexcinfo): - # unwrap potential exception info (see twisted trial support below) + def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None: + # Unwrap potential exception info (see twisted trial support below). rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) try: - excinfo = _pytest._code.ExceptionInfo(rawexcinfo) - # invoke the attributes to trigger storing the traceback - # trial causes some issue there + excinfo = _pytest._code.ExceptionInfo(rawexcinfo) # type: ignore[arg-type] + # Invoke the attributes to trigger storing the traceback + # trial causes some issue there. excinfo.value excinfo.traceback except TypeError: @@ -159,7 +184,7 @@ class TestCaseFunction(Function): fail("".join(values), pytrace=False) except (fail.Exception, KeyboardInterrupt): raise - except: # noqa + except BaseException: fail( "ERROR: Unknown Incompatible Exception " "representation:\n%r" % (rawexcinfo,), @@ -171,64 +196,92 @@ class TestCaseFunction(Function): excinfo = _pytest._code.ExceptionInfo.from_current() self.__dict__.setdefault("_excinfo", []).append(excinfo) - def addError(self, testcase, rawexcinfo): + def addError( + self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + ) -> None: + try: + if isinstance(rawexcinfo[1], exit.Exception): + exit(rawexcinfo[1].msg) + except TypeError: + pass self._addexcinfo(rawexcinfo) - def addFailure(self, testcase, rawexcinfo): + def addFailure( + self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + ) -> None: self._addexcinfo(rawexcinfo) - def addSkip(self, testcase, reason): + def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: try: skip(reason) except skip.Exception: - self._skipped_by_mark = True + self._store[skipped_by_mark_key] = True self._addexcinfo(sys.exc_info()) - def addExpectedFailure(self, testcase, rawexcinfo, reason=""): + def addExpectedFailure( + self, + testcase: "unittest.TestCase", + rawexcinfo: "_SysExcInfoType", + reason: str = "", + ) -> None: try: xfail(str(reason)) except xfail.Exception: self._addexcinfo(sys.exc_info()) - def addUnexpectedSuccess(self, testcase, reason=""): - self._unexpectedsuccess = reason + def addUnexpectedSuccess( + self, testcase: "unittest.TestCase", reason: str = "" + ) -> None: + self._store[unexpectedsuccess_key] = reason - def addSuccess(self, testcase): + def addSuccess(self, testcase: "unittest.TestCase") -> None: pass - def stopTest(self, testcase): + def stopTest(self, testcase: "unittest.TestCase") -> None: pass - def _handle_skip(self): - # implements the skipping machinery (see #2137) - # analog to pythons Lib/unittest/case.py:run - testMethod = getattr(self._testcase, self._testcase._testMethodName) - if getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr( - testMethod, "__unittest_skip__", False - ): - # If the class or method was skipped. - skip_why = getattr( - self._testcase.__class__, "__unittest_skip_why__", "" - ) or getattr(testMethod, "__unittest_skip_why__", "") - try: # PY3, unittest2 on PY2 - self._testcase._addSkip(self, self._testcase, skip_why) - except TypeError: # PY2 - if sys.version_info[0] != 2: - raise - self._testcase._addSkip(self, skip_why) - return True - return False + def _expecting_failure(self, test_method) -> bool: + """Return True if the given unittest method (or the entire class) is marked + with @expectedFailure.""" + expecting_failure_method = getattr( + test_method, "__unittest_expecting_failure__", False + ) + expecting_failure_class = getattr(self, "__unittest_expecting_failure__", False) + return bool(expecting_failure_class or expecting_failure_method) + + def runtest(self) -> None: + from _pytest.debugging import maybe_wrap_pytest_function_for_tracing + + assert self._testcase is not None + + maybe_wrap_pytest_function_for_tracing(self) - def runtest(self): - if self.config.pluginmanager.get_plugin("pdbinvoke") is None: - self._testcase(result=self) + # Let the unittest framework handle async functions. + if is_async_function(self.obj): + # Type ignored because self acts as the TestResult, but is not actually one. + self._testcase(result=self) # type: ignore[arg-type] else: - # disables tearDown and cleanups for post mortem debugging (see #1890) - if self._handle_skip(): - return - self._testcase.debug() + # When --pdb is given, we want to postpone calling tearDown() otherwise + # when entering the pdb prompt, tearDown() would have probably cleaned up + # instance variables, which makes it difficult to debug. + # Arguably we could always postpone tearDown(), but this changes the moment where the + # TestCase instance interacts with the results object, so better to only do it + # when absolutely needed. + if self.config.getoption("usepdb") and not _is_skipped(self.obj): + self._explicit_tearDown = self._testcase.tearDown + setattr(self._testcase, "tearDown", lambda *args: None) + + # We need to update the actual bound method with self.obj, because + # wrap_pytest_function_for_tracing replaces self.obj by a wrapper. + setattr(self._testcase, self.name, self.obj) + try: + self._testcase(result=self) # type: ignore[arg-type] + finally: + delattr(self._testcase, self.name) - def _prunetraceback(self, excinfo): + def _prunetraceback( + self, excinfo: _pytest._code.ExceptionInfo[BaseException] + ) -> None: Function._prunetraceback(self, excinfo) traceback = excinfo.traceback.filter( lambda x: not x.frame.f_globals.get("__unittest") @@ -238,7 +291,7 @@ class TestCaseFunction(Function): @hookimpl(tryfirst=True) -def pytest_runtest_makereport(item, call): +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None: if isinstance(item, TestCaseFunction): if item._excinfo: call.excinfo = item._excinfo.pop(0) @@ -247,14 +300,27 @@ def pytest_runtest_makereport(item, call): except AttributeError: pass + unittest = sys.modules.get("unittest") + if ( + unittest + and call.excinfo + and isinstance(call.excinfo.value, unittest.SkipTest) # type: ignore[attr-defined] + ): + excinfo = call.excinfo + # Let's substitute the excinfo with a pytest.skip one. + call2 = CallInfo[None].from_call( + lambda: pytest.skip(str(excinfo.value)), call.when + ) + call.excinfo = call2.excinfo + -# twisted trial support +# Twisted trial support. @hookimpl(hookwrapper=True) -def pytest_runtest_protocol(item): +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules: - ut = sys.modules["twisted.python.failure"] + ut = sys.modules["twisted.python.failure"] # type: Any Failure__init__ = ut.Failure.__init__ check_testcase_implements_trial_reporter() @@ -281,7 +347,7 @@ def pytest_runtest_protocol(item): yield -def check_testcase_implements_trial_reporter(done=[]): +def check_testcase_implements_trial_reporter(done: List[int] = []) -> None: if done: return from zope.interface import classImplements @@ -289,3 +355,8 @@ def check_testcase_implements_trial_reporter(done=[]): classImplements(TestCaseFunction, IReporter) done.append(1) + + +def _is_skipped(obj) -> bool: + """Return True if the given object has been marked with @unittest.skip.""" + return bool(getattr(obj, "__unittest_skip__", False)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py index 861010a1275..52e4d2b14cb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warning_types.py @@ -1,65 +1,69 @@ -# -*- coding: utf-8 -*- +from typing import Any +from typing import Generic +from typing import TypeVar + import attr +from _pytest.compat import final +from _pytest.compat import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Type # noqa: F401 (used in type string) + class PytestWarning(UserWarning): - """ - Bases: :class:`UserWarning`. + """Base class for all warnings emitted by pytest.""" - Base class for all warnings emitted by pytest. - """ + __module__ = "pytest" +@final class PytestAssertRewriteWarning(PytestWarning): - """ - Bases: :class:`PytestWarning`. + """Warning emitted by the pytest assert rewrite module.""" - Warning emitted by the pytest assert rewrite module. - """ + __module__ = "pytest" +@final class PytestCacheWarning(PytestWarning): - """ - Bases: :class:`PytestWarning`. + """Warning emitted by the cache plugin in various situations.""" - Warning emitted by the cache plugin in various situations. - """ + __module__ = "pytest" +@final class PytestConfigWarning(PytestWarning): - """ - Bases: :class:`PytestWarning`. + """Warning emitted for configuration issues.""" - Warning emitted for configuration issues. - """ + __module__ = "pytest" +@final class PytestCollectionWarning(PytestWarning): - """ - Bases: :class:`PytestWarning`. + """Warning emitted when pytest is not able to collect a file or symbol in a module.""" - Warning emitted when pytest is not able to collect a file or symbol in a module. - """ + __module__ = "pytest" +@final class PytestDeprecationWarning(PytestWarning, DeprecationWarning): - """ - Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`. + """Warning class for features that will be removed in a future version.""" - Warning class for features that will be removed in a future version. - """ + __module__ = "pytest" +@final class PytestExperimentalApiWarning(PytestWarning, FutureWarning): - """ - Bases: :class:`pytest.PytestWarning`, :class:`FutureWarning`. + """Warning category used to denote experiments in pytest. - Warning category used to denote experiments in pytest. Use sparingly as the API might change or even be - removed completely in future version + Use sparingly as the API might change or even be removed completely in a + future version. """ + __module__ = "pytest" + @classmethod - def simple(cls, apiname): + def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": return cls( "{apiname} is an experimental api that may change over time".format( apiname=apiname @@ -67,45 +71,45 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): ) +@final class PytestUnhandledCoroutineWarning(PytestWarning): - """ - Bases: :class:`PytestWarning`. + """Warning emitted for an unhandled coroutine. - Warning emitted when pytest encounters a test function which is a coroutine, - but it was not handled by any async-aware plugin. Coroutine test functions - are not natively supported. + A coroutine was encountered when collecting test functions, but was not + handled by any async-aware plugin. + Coroutine test functions are not natively supported. """ + __module__ = "pytest" + +@final class PytestUnknownMarkWarning(PytestWarning): - """ - Bases: :class:`PytestWarning`. + """Warning emitted on use of unknown markers. - Warning emitted on use of unknown markers. - See https://docs.pytest.org/en/latest/mark.html for details. + See :ref:`mark` for details. """ + __module__ = "pytest" -class RemovedInPytest4Warning(PytestDeprecationWarning): - """ - Bases: :class:`pytest.PytestDeprecationWarning`. - Warning class for features scheduled to be removed in pytest 4.0. - """ +_W = TypeVar("_W", bound=PytestWarning) +@final @attr.s -class UnformattedWarning(object): - """Used to hold warnings that need to format their message at runtime, as opposed to a direct message. +class UnformattedWarning(Generic[_W]): + """A warning meant to be formatted during runtime. - Using this class avoids to keep all the warning types and messages in this module, avoiding misuse. + This is used to hold warnings that need to format their message at runtime, + as opposed to a direct message. """ - category = attr.ib() - template = attr.ib() + category = attr.ib(type="Type[_W]") + template = attr.ib(type=str) - def format(self, **kwargs): - """Returns an instance of the warning category, formatted with given kwargs""" + def format(self, **kwargs: Any) -> _W: + """Return an instance of the warning category, formatted with given kwargs.""" return self.category(self.template.format(**kwargs)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py index a3debae462f..950d0bb3859 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/_pytest/warnings.py @@ -1,143 +1,99 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import sys import warnings from contextlib import contextmanager +from typing import Generator +from typing import Optional import pytest -from _pytest import compat - -SHOW_PYTEST_WARNINGS_ARG = "-Walways::pytest.RemovedInPytest4Warning" +from _pytest.compat import TYPE_CHECKING +from _pytest.config import apply_warning_filters +from _pytest.config import Config +from _pytest.config import parse_warning_filter +from _pytest.main import Session +from _pytest.nodes import Item +from _pytest.terminal import TerminalReporter - -def _setoption(wmod, arg): - """ - Copy of the warning._setoption function but does not escape arguments. - """ - parts = arg.split(":") - if len(parts) > 5: - raise wmod._OptionError("too many fields (max 5): %r" % (arg,)) - while len(parts) < 5: - parts.append("") - action, message, category, module, lineno = [s.strip() for s in parts] - action = wmod._getaction(action) - category = wmod._getcategory(category) - if lineno: - try: - lineno = int(lineno) - if lineno < 0: - raise ValueError - except (ValueError, OverflowError): - raise wmod._OptionError("invalid lineno %r" % (lineno,)) - else: - lineno = 0 - wmod.filterwarnings(action, message, category, module, lineno) - - -def pytest_addoption(parser): - group = parser.getgroup("pytest-warnings") - group.addoption( - "-W", - "--pythonwarnings", - action="append", - help="set which warnings to report, see -W option of python itself.", - ) - parser.addini( - "filterwarnings", - type="linelist", - help="Each line specifies a pattern for " - "warnings.filterwarnings. " - "Processed after -W and --pythonwarnings.", - ) +if TYPE_CHECKING: + from typing_extensions import Literal -def pytest_configure(config): +def pytest_configure(config: Config) -> None: config.addinivalue_line( "markers", "filterwarnings(warning): add a warning filter to the given test. " - "see https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings ", + "see https://docs.pytest.org/en/stable/warnings.html#pytest-mark-filterwarnings ", ) @contextmanager -def catch_warnings_for_item(config, ihook, when, item): - """ - Context manager that catches warnings generated in the contained execution block. +def catch_warnings_for_item( + config: Config, + ihook, + when: "Literal['config', 'collect', 'runtest']", + item: Optional[Item], +) -> Generator[None, None, None]: + """Context manager that catches warnings generated in the contained execution block. ``item`` can be None if we are not in the context of an item execution. - Each warning captured triggers the ``pytest_warning_captured`` hook. + Each warning captured triggers the ``pytest_warning_recorded`` hook. """ - cmdline_filters = config.getoption("pythonwarnings") or [] - inifilters = config.getini("filterwarnings") + config_filters = config.getini("filterwarnings") + cmdline_filters = config.known_args_namespace.pythonwarnings or [] with warnings.catch_warnings(record=True) as log: + # mypy can't infer that record=True means log is not None; help it. + assert log is not None if not sys.warnoptions: - # if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908) + # If user is not explicitly configuring warning filters, show deprecation warnings by default (#2908). warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) - warnings.filterwarnings("error", category=pytest.RemovedInPytest4Warning) - - # filters should have this precedence: mark, cmdline options, ini - # filters should be applied in the inverse order of precedence - for arg in inifilters: - _setoption(warnings, arg) - - for arg in cmdline_filters: - warnings._setoption(arg) + apply_warning_filters(config_filters, cmdline_filters) + # apply filters from "filterwarnings" marks + nodeid = "" if item is None else item.nodeid if item is not None: for mark in item.iter_markers(name="filterwarnings"): for arg in mark.args: - _setoption(warnings, arg) + warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) yield for warning_message in log: ihook.pytest_warning_captured.call_historic( - kwargs=dict(warning_message=warning_message, when=when, item=item) + kwargs=dict( + warning_message=warning_message, + when=when, + item=item, + location=None, + ) + ) + ihook.pytest_warning_recorded.call_historic( + kwargs=dict( + warning_message=warning_message, + nodeid=nodeid, + when=when, + location=None, + ) ) -def warning_record_to_str(warning_message): - """Convert a warnings.WarningMessage to a string. - - This takes lot of unicode shenaningans into account for Python 2. - When Python 2 support is dropped this function can be greatly simplified. - """ +def warning_record_to_str(warning_message: warnings.WarningMessage) -> str: + """Convert a warnings.WarningMessage to a string.""" warn_msg = warning_message.message - unicode_warning = False - if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args): - new_args = [] - for m in warn_msg.args: - new_args.append( - compat.ascii_escaped(m) if isinstance(m, compat.UNICODE_TYPES) else m - ) - unicode_warning = list(warn_msg.args) != new_args - warn_msg.args = new_args - msg = warnings.formatwarning( - warn_msg, + str(warn_msg), warning_message.category, warning_message.filename, warning_message.lineno, warning_message.line, ) - if unicode_warning: - warnings.warn( - "Warning is using unicode non convertible to ascii, " - "converting to a safe representation:\n {!r}".format(compat.safe_str(msg)), - UnicodeWarning, - ) return msg @pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_runtest_protocol(item): +def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: with catch_warnings_for_item( config=item.config, ihook=item.ihook, when="runtest", item=item ): @@ -145,7 +101,7 @@ def pytest_runtest_protocol(item): @pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_collection(session): +def pytest_collection(session: Session) -> Generator[None, None, None]: config = session.config with catch_warnings_for_item( config=config, ihook=config.hook, when="collect", item=None @@ -154,7 +110,9 @@ def pytest_collection(session): @pytest.hookimpl(hookwrapper=True) -def pytest_terminal_summary(terminalreporter): +def pytest_terminal_summary( + terminalreporter: TerminalReporter, +) -> Generator[None, None, None]: config = terminalreporter.config with catch_warnings_for_item( config=config, ihook=config.hook, when="config", item=None @@ -162,19 +120,20 @@ def pytest_terminal_summary(terminalreporter): yield -def _issue_warning_captured(warning, hook, stacklevel): - """ - This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage: - at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured - hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891. +@pytest.hookimpl(hookwrapper=True) +def pytest_sessionfinish(session: Session) -> Generator[None, None, None]: + config = session.config + with catch_warnings_for_item( + config=config, ihook=config.hook, when="config", item=None + ): + yield - :param warning: the warning instance. - :param hook: the hook caller - :param stacklevel: stacklevel forwarded to warnings.warn - """ - with warnings.catch_warnings(record=True) as records: - warnings.simplefilter("always", type(warning)) - warnings.warn(warning, stacklevel=stacklevel) - hook.pytest_warning_captured.call_historic( - kwargs=dict(warning_message=records[0], when="config", item=None) - ) + +@pytest.hookimpl(hookwrapper=True) +def pytest_load_initial_conftests( + early_config: "Config", +) -> Generator[None, None, None]: + with catch_warnings_for_item( + config=early_config, ihook=early_config.hook, when="config", item=None + ): + yield diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/PKG-INFO b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/PKG-INFO new file mode 100644 index 00000000000..9a387bfaf2c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/PKG-INFO @@ -0,0 +1,192 @@ +Metadata-Version: 2.1 +Name: pytest +Version: 6.1.1 +Summary: pytest: simple powerful testing with Python +Home-page: https://docs.pytest.org/en/latest/ +Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others +License: MIT +Project-URL: Source, https://github.com/pytest-dev/pytest +Project-URL: Tracker, https://github.com/pytest-dev/pytest/issues +Description: .. image:: https://docs.pytest.org/en/stable/_static/pytest1.png + :target: https://docs.pytest.org/en/stable/ + :align: center + :alt: pytest + + + ------ + + .. image:: https://img.shields.io/pypi/v/pytest.svg + :target: https://pypi.org/project/pytest/ + + .. image:: https://img.shields.io/conda/vn/conda-forge/pytest.svg + :target: https://anaconda.org/conda-forge/pytest + + .. image:: https://img.shields.io/pypi/pyversions/pytest.svg + :target: https://pypi.org/project/pytest/ + + .. image:: https://codecov.io/gh/pytest-dev/pytest/branch/master/graph/badge.svg + :target: https://codecov.io/gh/pytest-dev/pytest + :alt: Code coverage Status + + .. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master + :target: https://travis-ci.org/pytest-dev/pytest + + .. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg + :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain + + .. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + + .. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg + :target: https://www.codetriage.com/pytest-dev/pytest + + .. image:: https://readthedocs.org/projects/pytest/badge/?version=latest + :target: https://pytest.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + + The ``pytest`` framework makes it easy to write small tests, yet + scales to support complex functional testing for applications and libraries. + + An example of a simple test: + + .. code-block:: python + + # content of test_sample.py + def inc(x): + return x + 1 + + + def test_answer(): + assert inc(3) == 5 + + + To execute it:: + + $ pytest + ============================= test session starts ============================= + collected 1 items + + test_sample.py F + + ================================== FAILURES =================================== + _________________________________ test_answer _________________________________ + + def test_answer(): + > assert inc(3) == 5 + E assert 4 == 5 + E + where 4 = inc(3) + + test_sample.py:5: AssertionError + ========================== 1 failed in 0.04 seconds =========================== + + + Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <https://docs.pytest.org/en/stable/getting-started.html#our-first-test-run>`_ for more examples. + + + Features + -------- + + - Detailed info on failing `assert statements <https://docs.pytest.org/en/stable/assert.html>`_ (no need to remember ``self.assert*`` names) + + - `Auto-discovery + <https://docs.pytest.org/en/stable/goodpractices.html#python-test-discovery>`_ + of test modules and functions + + - `Modular fixtures <https://docs.pytest.org/en/stable/fixture.html>`_ for + managing small or parametrized long-lived test resources + + - Can run `unittest <https://docs.pytest.org/en/stable/unittest.html>`_ (or trial), + `nose <https://docs.pytest.org/en/stable/nose.html>`_ test suites out of the box + + - Python 3.5+ and PyPy3 + + - Rich plugin architecture, with over 850+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community + + + Documentation + ------------- + + For full documentation, including installation, tutorials and PDF documents, please see https://docs.pytest.org/en/stable/. + + + Bugs/Requests + ------------- + + Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features. + + + Changelog + --------- + + Consult the `Changelog <https://docs.pytest.org/en/stable/changelog.html>`__ page for fixes and enhancements of each version. + + + Support pytest + -------------- + + `Open Collective`_ is an online funding platform for open and transparent communities. + It provides tools to raise money and share your finances in full transparency. + + It is the platform of choice for individuals and companies that want to make one-time or + monthly donations directly to the project. + + See more details in the `pytest collective`_. + + .. _Open Collective: https://opencollective.com + .. _pytest collective: https://opencollective.com/pytest + + + pytest for enterprise + --------------------- + + Available as part of the Tidelift Subscription. + + The maintainers of pytest and thousands of other packages are working with Tidelift to deliver commercial support and + maintenance for the open source dependencies you use to build your applications. + Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. + + `Learn more. <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_ + + Security + ^^^^^^^^ + + pytest has never been associated with a security vulnerability, but in any case, to report a + security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_. + Tidelift will coordinate the fix and disclosure. + + + License + ------- + + Copyright Holger Krekel and others, 2004-2020. + + Distributed under the terms of the `MIT`_ license, pytest is free and open source software. + + .. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE + +Keywords: test,unittest +Platform: unix +Platform: linux +Platform: osx +Platform: cygwin +Platform: win32 +Classifier: Development Status :: 6 - Mature +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Utilities +Requires-Python: >=3.5 +Description-Content-Type: text/x-rst +Provides-Extra: checkqa_mypy +Provides-Extra: testing diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/SOURCES.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/SOURCES.txt new file mode 100644 index 00000000000..98acdd0ef7c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/SOURCES.txt @@ -0,0 +1,500 @@ +.coveragerc +.gitattributes +.gitblameignore +.gitignore +.pre-commit-config.yaml +.readthedocs.yml +.travis.yml +AUTHORS +CHANGELOG.rst +CITATION +CODE_OF_CONDUCT.md +CONTRIBUTING.rst +LICENSE +OPENCOLLECTIVE.rst +README.rst +RELEASING.rst +TIDELIFT.rst +codecov.yml +pyproject.toml +setup.cfg +setup.py +tox.ini +.github/FUNDING.yml +.github/PULL_REQUEST_TEMPLATE.md +.github/config.yml +.github/labels.toml +.github/ISSUE_TEMPLATE/1_bug_report.md +.github/ISSUE_TEMPLATE/2_feature_request.md +.github/ISSUE_TEMPLATE/config.yml +.github/workflows/main.yml +.github/workflows/release-on-comment.yml +bench/bench.py +bench/bench_argcomplete.py +bench/empty.py +bench/manyparam.py +bench/skip.py +changelog/README.rst +changelog/_template.rst +doc/en/Makefile +doc/en/adopt.rst +doc/en/assert.rst +doc/en/backwards-compatibility.rst +doc/en/bash-completion.rst +doc/en/builtin.rst +doc/en/cache.rst +doc/en/capture.rst +doc/en/changelog.rst +doc/en/conf.py +doc/en/conftest.py +doc/en/contact.rst +doc/en/contents.rst +doc/en/contributing.rst +doc/en/customize.rst +doc/en/deprecations.rst +doc/en/development_guide.rst +doc/en/doctest.rst +doc/en/existingtestsuite.rst +doc/en/fixture.rst +doc/en/flaky.rst +doc/en/funcarg_compare.rst +doc/en/funcargs.rst +doc/en/getting-started.rst +doc/en/goodpractices.rst +doc/en/historical-notes.rst +doc/en/index.rst +doc/en/license.rst +doc/en/logging.rst +doc/en/mark.rst +doc/en/monkeypatch.rst +doc/en/naming20.rst +doc/en/nose.rst +doc/en/parametrize.rst +doc/en/plugins.rst +doc/en/projects.rst +doc/en/py27-py34-deprecation.rst +doc/en/pytest.ini +doc/en/pythonpath.rst +doc/en/recwarn.rst +doc/en/reference.rst +doc/en/requirements.txt +doc/en/skipping.rst +doc/en/sponsor.rst +doc/en/talks.rst +doc/en/tidelift.rst +doc/en/tmpdir.rst +doc/en/unittest.rst +doc/en/usage.rst +doc/en/warnings.rst +doc/en/writing_plugins.rst +doc/en/xunit_setup.rst +doc/en/yieldfixture.rst +doc/en/_templates/globaltoc.html +doc/en/_templates/layout.html +doc/en/_templates/links.html +doc/en/_templates/relations.html +doc/en/_templates/sidebarintro.html +doc/en/_templates/slim_searchbox.html +doc/en/announce/index.rst +doc/en/announce/release-2.0.0.rst +doc/en/announce/release-2.0.1.rst +doc/en/announce/release-2.0.2.rst +doc/en/announce/release-2.0.3.rst +doc/en/announce/release-2.1.0.rst +doc/en/announce/release-2.1.1.rst +doc/en/announce/release-2.1.2.rst +doc/en/announce/release-2.1.3.rst +doc/en/announce/release-2.2.0.rst +doc/en/announce/release-2.2.1.rst +doc/en/announce/release-2.2.2.rst +doc/en/announce/release-2.2.4.rst +doc/en/announce/release-2.3.0.rst +doc/en/announce/release-2.3.1.rst +doc/en/announce/release-2.3.2.rst +doc/en/announce/release-2.3.3.rst +doc/en/announce/release-2.3.4.rst +doc/en/announce/release-2.3.5.rst +doc/en/announce/release-2.4.0.rst +doc/en/announce/release-2.4.1.rst +doc/en/announce/release-2.4.2.rst +doc/en/announce/release-2.5.0.rst +doc/en/announce/release-2.5.1.rst +doc/en/announce/release-2.5.2.rst +doc/en/announce/release-2.6.0.rst +doc/en/announce/release-2.6.1.rst +doc/en/announce/release-2.6.2.rst +doc/en/announce/release-2.6.3.rst +doc/en/announce/release-2.7.0.rst +doc/en/announce/release-2.7.1.rst +doc/en/announce/release-2.7.2.rst +doc/en/announce/release-2.8.2.rst +doc/en/announce/release-2.8.3.rst +doc/en/announce/release-2.8.4.rst +doc/en/announce/release-2.8.5.rst +doc/en/announce/release-2.8.6.rst +doc/en/announce/release-2.8.7.rst +doc/en/announce/release-2.9.0.rst +doc/en/announce/release-2.9.1.rst +doc/en/announce/release-2.9.2.rst +doc/en/announce/release-3.0.0.rst +doc/en/announce/release-3.0.1.rst +doc/en/announce/release-3.0.2.rst +doc/en/announce/release-3.0.3.rst +doc/en/announce/release-3.0.4.rst +doc/en/announce/release-3.0.5.rst +doc/en/announce/release-3.0.6.rst +doc/en/announce/release-3.0.7.rst +doc/en/announce/release-3.1.0.rst +doc/en/announce/release-3.1.1.rst +doc/en/announce/release-3.1.2.rst +doc/en/announce/release-3.1.3.rst +doc/en/announce/release-3.10.0.rst +doc/en/announce/release-3.10.1.rst +doc/en/announce/release-3.2.0.rst +doc/en/announce/release-3.2.1.rst +doc/en/announce/release-3.2.2.rst +doc/en/announce/release-3.2.3.rst +doc/en/announce/release-3.2.4.rst +doc/en/announce/release-3.2.5.rst +doc/en/announce/release-3.3.0.rst +doc/en/announce/release-3.3.1.rst +doc/en/announce/release-3.3.2.rst +doc/en/announce/release-3.4.0.rst +doc/en/announce/release-3.4.1.rst +doc/en/announce/release-3.4.2.rst +doc/en/announce/release-3.5.0.rst +doc/en/announce/release-3.5.1.rst +doc/en/announce/release-3.6.0.rst +doc/en/announce/release-3.6.1.rst +doc/en/announce/release-3.6.2.rst +doc/en/announce/release-3.6.3.rst +doc/en/announce/release-3.6.4.rst +doc/en/announce/release-3.7.0.rst +doc/en/announce/release-3.7.1.rst +doc/en/announce/release-3.7.2.rst +doc/en/announce/release-3.7.3.rst +doc/en/announce/release-3.7.4.rst +doc/en/announce/release-3.8.0.rst +doc/en/announce/release-3.8.1.rst +doc/en/announce/release-3.8.2.rst +doc/en/announce/release-3.9.0.rst +doc/en/announce/release-3.9.1.rst +doc/en/announce/release-3.9.2.rst +doc/en/announce/release-3.9.3.rst +doc/en/announce/release-4.0.0.rst +doc/en/announce/release-4.0.1.rst +doc/en/announce/release-4.0.2.rst +doc/en/announce/release-4.1.0.rst +doc/en/announce/release-4.1.1.rst +doc/en/announce/release-4.2.0.rst +doc/en/announce/release-4.2.1.rst +doc/en/announce/release-4.3.0.rst +doc/en/announce/release-4.3.1.rst +doc/en/announce/release-4.4.0.rst +doc/en/announce/release-4.4.1.rst +doc/en/announce/release-4.4.2.rst +doc/en/announce/release-4.5.0.rst +doc/en/announce/release-4.6.0.rst +doc/en/announce/release-4.6.1.rst +doc/en/announce/release-4.6.2.rst +doc/en/announce/release-4.6.3.rst +doc/en/announce/release-4.6.4.rst +doc/en/announce/release-4.6.5.rst +doc/en/announce/release-4.6.6.rst +doc/en/announce/release-4.6.7.rst +doc/en/announce/release-4.6.8.rst +doc/en/announce/release-4.6.9.rst +doc/en/announce/release-5.0.0.rst +doc/en/announce/release-5.0.1.rst +doc/en/announce/release-5.1.0.rst +doc/en/announce/release-5.1.1.rst +doc/en/announce/release-5.1.2.rst +doc/en/announce/release-5.1.3.rst +doc/en/announce/release-5.2.0.rst +doc/en/announce/release-5.2.1.rst +doc/en/announce/release-5.2.2.rst +doc/en/announce/release-5.2.3.rst +doc/en/announce/release-5.2.4.rst +doc/en/announce/release-5.3.0.rst +doc/en/announce/release-5.3.1.rst +doc/en/announce/release-5.3.2.rst +doc/en/announce/release-5.3.3.rst +doc/en/announce/release-5.3.4.rst +doc/en/announce/release-5.3.5.rst +doc/en/announce/release-5.4.0.rst +doc/en/announce/release-5.4.1.rst +doc/en/announce/release-5.4.2.rst +doc/en/announce/release-5.4.3.rst +doc/en/announce/release-6.0.0.rst +doc/en/announce/release-6.0.0rc1.rst +doc/en/announce/release-6.0.1.rst +doc/en/announce/release-6.0.2.rst +doc/en/announce/release-6.1.0.rst +doc/en/announce/release-6.1.1.rst +doc/en/announce/sprint2016.rst +doc/en/example/attic.rst +doc/en/example/conftest.py +doc/en/example/index.rst +doc/en/example/markers.rst +doc/en/example/multipython.py +doc/en/example/nonpython.rst +doc/en/example/parametrize.rst +doc/en/example/pythoncollection.py +doc/en/example/pythoncollection.rst +doc/en/example/reportingdemo.rst +doc/en/example/simple.rst +doc/en/example/special.rst +doc/en/example/xfail_demo.py +doc/en/example/assertion/failure_demo.py +doc/en/example/assertion/test_failures.py +doc/en/example/assertion/test_setup_flow_example.py +doc/en/example/assertion/global_testmodule_config/conftest.py +doc/en/example/assertion/global_testmodule_config/test_hello_world.py +doc/en/example/fixtures/test_fixtures_order.py +doc/en/example/nonpython/__init__.py +doc/en/example/nonpython/conftest.py +doc/en/example/nonpython/test_simple.yaml +doc/en/img/cramer2.png +doc/en/img/favicon.png +doc/en/img/freiburg2.jpg +doc/en/img/gaynor3.png +doc/en/img/keleshev.png +doc/en/img/pullrequest.png +doc/en/img/pylib.png +doc/en/img/pytest1.png +doc/en/img/theuni.png +doc/en/proposals/parametrize_with_fixtures.rst +extra/get_issues.py +extra/setup-py.test/setup.py +scripts/append_codecov_token.py +scripts/publish-gh-release-notes.py +scripts/release-on-comment.py +scripts/release.minor.rst +scripts/release.patch.rst +scripts/release.py +scripts/report-coverage.sh +scripts/towncrier-draft-to-file.py +src/_pytest/__init__.py +src/_pytest/_argcomplete.py +src/_pytest/_version.py +src/_pytest/cacheprovider.py +src/_pytest/capture.py +src/_pytest/compat.py +src/_pytest/debugging.py +src/_pytest/deprecated.py +src/_pytest/doctest.py +src/_pytest/faulthandler.py +src/_pytest/fixtures.py +src/_pytest/freeze_support.py +src/_pytest/helpconfig.py +src/_pytest/hookspec.py +src/_pytest/junitxml.py +src/_pytest/logging.py +src/_pytest/main.py +src/_pytest/monkeypatch.py +src/_pytest/nodes.py +src/_pytest/nose.py +src/_pytest/outcomes.py +src/_pytest/pastebin.py +src/_pytest/pathlib.py +src/_pytest/py.typed +src/_pytest/pytester.py +src/_pytest/python.py +src/_pytest/python_api.py +src/_pytest/recwarn.py +src/_pytest/reports.py +src/_pytest/runner.py +src/_pytest/setuponly.py +src/_pytest/setupplan.py +src/_pytest/skipping.py +src/_pytest/stepwise.py +src/_pytest/store.py +src/_pytest/terminal.py +src/_pytest/timing.py +src/_pytest/tmpdir.py +src/_pytest/unittest.py +src/_pytest/warning_types.py +src/_pytest/warnings.py +src/_pytest/_code/__init__.py +src/_pytest/_code/code.py +src/_pytest/_code/source.py +src/_pytest/_io/__init__.py +src/_pytest/_io/saferepr.py +src/_pytest/_io/terminalwriter.py +src/_pytest/_io/wcwidth.py +src/_pytest/assertion/__init__.py +src/_pytest/assertion/rewrite.py +src/_pytest/assertion/truncate.py +src/_pytest/assertion/util.py +src/_pytest/config/__init__.py +src/_pytest/config/argparsing.py +src/_pytest/config/exceptions.py +src/_pytest/config/findpaths.py +src/_pytest/mark/__init__.py +src/_pytest/mark/expression.py +src/_pytest/mark/structures.py +src/pytest/__init__.py +src/pytest/__main__.py +src/pytest/collect.py +src/pytest/py.typed +src/pytest.egg-info/PKG-INFO +src/pytest.egg-info/SOURCES.txt +src/pytest.egg-info/dependency_links.txt +src/pytest.egg-info/entry_points.txt +src/pytest.egg-info/not-zip-safe +src/pytest.egg-info/requires.txt +src/pytest.egg-info/top_level.txt +testing/acceptance_test.py +testing/conftest.py +testing/deprecated_test.py +testing/test_argcomplete.py +testing/test_assertion.py +testing/test_assertrewrite.py +testing/test_cacheprovider.py +testing/test_capture.py +testing/test_collection.py +testing/test_compat.py +testing/test_config.py +testing/test_conftest.py +testing/test_debugging.py +testing/test_doctest.py +testing/test_entry_points.py +testing/test_error_diffs.py +testing/test_faulthandler.py +testing/test_findpaths.py +testing/test_helpconfig.py +testing/test_junitxml.py +testing/test_link_resolve.py +testing/test_main.py +testing/test_mark.py +testing/test_mark_expression.py +testing/test_meta.py +testing/test_monkeypatch.py +testing/test_nodes.py +testing/test_nose.py +testing/test_parseopt.py +testing/test_pastebin.py +testing/test_pathlib.py +testing/test_pluginmanager.py +testing/test_pytester.py +testing/test_recwarn.py +testing/test_reports.py +testing/test_runner.py +testing/test_runner_xunit.py +testing/test_session.py +testing/test_setuponly.py +testing/test_setupplan.py +testing/test_skipping.py +testing/test_stepwise.py +testing/test_store.py +testing/test_terminal.py +testing/test_tmpdir.py +testing/test_unittest.py +testing/test_warning_types.py +testing/test_warnings.py +testing/typing_checks.py +testing/code/test_code.py +testing/code/test_excinfo.py +testing/code/test_source.py +testing/example_scripts/README.rst +testing/example_scripts/issue_519.py +testing/example_scripts/junit-10.xsd +testing/example_scripts/pytest.ini +testing/example_scripts/acceptance/fixture_mock_integration.py +testing/example_scripts/collect/collect_init_tests/pytest.ini +testing/example_scripts/collect/collect_init_tests/tests/__init__.py +testing/example_scripts/collect/collect_init_tests/tests/test_foo.py +testing/example_scripts/collect/package_infinite_recursion/__init__.pyi +testing/example_scripts/collect/package_infinite_recursion/conftest.py +testing/example_scripts/collect/package_infinite_recursion/tests/__init__.py +testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py +testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py +testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py +testing/example_scripts/config/collect_pytest_prefix/__init__.pyi +testing/example_scripts/config/collect_pytest_prefix/conftest.py +testing/example_scripts/config/collect_pytest_prefix/test_foo.py +testing/example_scripts/conftest_usageerror/__init__.pyi +testing/example_scripts/conftest_usageerror/conftest.py +testing/example_scripts/dataclasses/test_compare_dataclasses.py +testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py +testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py +testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py +testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py +testing/example_scripts/fixtures/test_fixture_named_request.py +testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py +testing/example_scripts/fixtures/custom_item/__init__.pyi +testing/example_scripts/fixtures/custom_item/conftest.py +testing/example_scripts/fixtures/custom_item/foo/__init__.py +testing/example_scripts/fixtures/custom_item/foo/test_foo.py +testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py +testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py +testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py +testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py +testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py +testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/__init__.py +testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py +testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py +testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/__init__.py +testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/__init__.py +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py +testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py +testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi +testing/example_scripts/issue88_initial_file_multinodes/conftest.py +testing/example_scripts/issue88_initial_file_multinodes/test_hello.py +testing/example_scripts/marks/marks_considered_keywords/__init__.pyi +testing/example_scripts/marks/marks_considered_keywords/conftest.py +testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py +testing/example_scripts/perf_examples/collect_stats/.gitignore +testing/example_scripts/perf_examples/collect_stats/generate_folders.py +testing/example_scripts/perf_examples/collect_stats/template_test.py +testing/example_scripts/tmpdir/tmpdir_fixture.py +testing/example_scripts/unittest/test_parametrized_fixture_error_message.py +testing/example_scripts/unittest/test_setup_skip.py +testing/example_scripts/unittest/test_setup_skip_class.py +testing/example_scripts/unittest/test_setup_skip_module.py +testing/example_scripts/unittest/test_unittest_asyncio.py +testing/example_scripts/unittest/test_unittest_asynctest.py +testing/example_scripts/unittest/test_unittest_plain_async.py +testing/example_scripts/warnings/test_group_warnings_by_message.py +testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py +testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py +testing/examples/test_issue519.py +testing/freeze/.gitignore +testing/freeze/create_executable.py +testing/freeze/runtests_script.py +testing/freeze/tox_run.py +testing/freeze/tests/test_doctest.txt +testing/freeze/tests/test_trivial.py +testing/io/test_saferepr.py +testing/io/test_terminalwriter.py +testing/io/test_wcwidth.py +testing/logging/test_fixture.py +testing/logging/test_formatter.py +testing/logging/test_reporting.py +testing/plugins_integration/.gitignore +testing/plugins_integration/README.rst +testing/plugins_integration/bdd_wallet.feature +testing/plugins_integration/bdd_wallet.py +testing/plugins_integration/django_settings.py +testing/plugins_integration/pytest.ini +testing/plugins_integration/pytest_anyio_integration.py +testing/plugins_integration/pytest_asyncio_integration.py +testing/plugins_integration/pytest_mock_integration.py +testing/plugins_integration/pytest_trio_integration.py +testing/plugins_integration/pytest_twisted_integration.py +testing/plugins_integration/simple_integration.py +testing/python/approx.py +testing/python/collect.py +testing/python/fixtures.py +testing/python/integration.py +testing/python/metafunc.py +testing/python/raises.py +testing/python/show_fixtures_per_test.py
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/dependency_links.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/dependency_links.txt new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/entry_points.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/entry_points.txt new file mode 100644 index 00000000000..0267c75b77e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] +py.test = pytest:console_main +pytest = pytest:console_main + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/not-zip-safe b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/not-zip-safe new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/requires.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/requires.txt new file mode 100644 index 00000000000..bc1844a4a3c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/requires.txt @@ -0,0 +1,27 @@ +attrs>=17.4.0 +iniconfig +packaging +pluggy<1.0,>=0.12 +py>=1.8.2 +toml + +[:python_version < "3.6"] +pathlib2>=2.2.0 + +[:python_version < "3.8"] +importlib-metadata>=0.12 + +[:sys_platform == "win32"] +atomicwrites>=1.0 +colorama + +[checkqa_mypy] +mypy==0.780 + +[testing] +argcomplete +hypothesis>=3.56 +mock +nose +requests +xmlschema diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/top_level.txt b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/top_level.txt new file mode 100644 index 00000000000..e94857af964 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.egg-info/top_level.txt @@ -0,0 +1,2 @@ +_pytest +pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.py deleted file mode 100644 index ccc77b476f5..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest.py +++ /dev/null @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- -# PYTHON_ARGCOMPLETE_OK -""" -pytest: unit and functional testing with Python. -""" -# else we are imported -from _pytest import __version__ -from _pytest.assertion import register_assert_rewrite -from _pytest.config import cmdline -from _pytest.config import hookimpl -from _pytest.config import hookspec -from _pytest.config import main -from _pytest.config import UsageError -from _pytest.debugging import pytestPDB as __pytestPDB -from _pytest.fixtures import fillfixtures as _fillfuncargs -from _pytest.fixtures import fixture -from _pytest.fixtures import yield_fixture -from _pytest.freeze_support import freeze_includes -from _pytest.main import Session -from _pytest.mark import MARK_GEN as mark -from _pytest.mark import param -from _pytest.nodes import Collector -from _pytest.nodes import File -from _pytest.nodes import Item -from _pytest.outcomes import exit -from _pytest.outcomes import fail -from _pytest.outcomes import importorskip -from _pytest.outcomes import skip -from _pytest.outcomes import xfail -from _pytest.python import Class -from _pytest.python import Function -from _pytest.python import Instance -from _pytest.python import Module -from _pytest.python import Package -from _pytest.python_api import approx -from _pytest.python_api import raises -from _pytest.recwarn import deprecated_call -from _pytest.recwarn import warns -from _pytest.warning_types import PytestAssertRewriteWarning -from _pytest.warning_types import PytestCacheWarning -from _pytest.warning_types import PytestCollectionWarning -from _pytest.warning_types import PytestConfigWarning -from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warning_types import PytestExperimentalApiWarning -from _pytest.warning_types import PytestUnhandledCoroutineWarning -from _pytest.warning_types import PytestUnknownMarkWarning -from _pytest.warning_types import PytestWarning -from _pytest.warning_types import RemovedInPytest4Warning - -set_trace = __pytestPDB.set_trace - -__all__ = [ - "__version__", - "_fillfuncargs", - "approx", - "Class", - "cmdline", - "Collector", - "deprecated_call", - "exit", - "fail", - "File", - "fixture", - "freeze_includes", - "Function", - "hookimpl", - "hookspec", - "importorskip", - "Instance", - "Item", - "main", - "mark", - "Module", - "Package", - "param", - "PytestAssertRewriteWarning", - "PytestCacheWarning", - "PytestCollectionWarning", - "PytestConfigWarning", - "PytestDeprecationWarning", - "PytestExperimentalApiWarning", - "PytestUnhandledCoroutineWarning", - "PytestUnknownMarkWarning", - "PytestWarning", - "raises", - "register_assert_rewrite", - "RemovedInPytest4Warning", - "Session", - "set_trace", - "skip", - "UsageError", - "warns", - "xfail", - "yield_fixture", -] - -if __name__ == "__main__": - # if run as a script or by 'python -m pytest' - # we trigger the below "else" condition by the following import - import pytest - - raise SystemExit(pytest.main()) -else: - - from _pytest.compat import _setup_collect_fakemodule - - _setup_collect_fakemodule() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py new file mode 100644 index 00000000000..c4c28191877 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/__init__.py @@ -0,0 +1,97 @@ +# PYTHON_ARGCOMPLETE_OK +"""pytest: unit and functional testing with Python.""" +from . import collect +from _pytest import __version__ +from _pytest.assertion import register_assert_rewrite +from _pytest.config import cmdline +from _pytest.config import console_main +from _pytest.config import ExitCode +from _pytest.config import hookimpl +from _pytest.config import hookspec +from _pytest.config import main +from _pytest.config import UsageError +from _pytest.debugging import pytestPDB as __pytestPDB +from _pytest.fixtures import fillfixtures as _fillfuncargs +from _pytest.fixtures import fixture +from _pytest.fixtures import FixtureLookupError +from _pytest.fixtures import yield_fixture +from _pytest.freeze_support import freeze_includes +from _pytest.main import Session +from _pytest.mark import MARK_GEN as mark +from _pytest.mark import param +from _pytest.nodes import Collector +from _pytest.nodes import File +from _pytest.nodes import Item +from _pytest.outcomes import exit +from _pytest.outcomes import fail +from _pytest.outcomes import importorskip +from _pytest.outcomes import skip +from _pytest.outcomes import xfail +from _pytest.python import Class +from _pytest.python import Function +from _pytest.python import Instance +from _pytest.python import Module +from _pytest.python import Package +from _pytest.python_api import approx +from _pytest.python_api import raises +from _pytest.recwarn import deprecated_call +from _pytest.recwarn import warns +from _pytest.warning_types import PytestAssertRewriteWarning +from _pytest.warning_types import PytestCacheWarning +from _pytest.warning_types import PytestCollectionWarning +from _pytest.warning_types import PytestConfigWarning +from _pytest.warning_types import PytestDeprecationWarning +from _pytest.warning_types import PytestExperimentalApiWarning +from _pytest.warning_types import PytestUnhandledCoroutineWarning +from _pytest.warning_types import PytestUnknownMarkWarning +from _pytest.warning_types import PytestWarning + +set_trace = __pytestPDB.set_trace + +__all__ = [ + "__version__", + "_fillfuncargs", + "approx", + "Class", + "cmdline", + "collect", + "Collector", + "console_main", + "deprecated_call", + "exit", + "ExitCode", + "fail", + "File", + "fixture", + "FixtureLookupError", + "freeze_includes", + "Function", + "hookimpl", + "hookspec", + "importorskip", + "Instance", + "Item", + "main", + "mark", + "Module", + "Package", + "param", + "PytestAssertRewriteWarning", + "PytestCacheWarning", + "PytestCollectionWarning", + "PytestConfigWarning", + "PytestDeprecationWarning", + "PytestExperimentalApiWarning", + "PytestUnhandledCoroutineWarning", + "PytestUnknownMarkWarning", + "PytestWarning", + "raises", + "register_assert_rewrite", + "Session", + "set_trace", + "skip", + "UsageError", + "warns", + "xfail", + "yield_fixture", +] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py new file mode 100644 index 00000000000..b170152937b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/__main__.py @@ -0,0 +1,5 @@ +"""The pytest entry point.""" +import pytest + +if __name__ == "__main__": + raise SystemExit(pytest.console_main()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/collect.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/collect.py new file mode 100644 index 00000000000..2edf4470f4d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/collect.py @@ -0,0 +1,39 @@ +import sys +import warnings +from types import ModuleType +from typing import Any +from typing import List + +import pytest +from _pytest.deprecated import PYTEST_COLLECT_MODULE + +COLLECT_FAKEMODULE_ATTRIBUTES = [ + "Collector", + "Module", + "Function", + "Instance", + "Session", + "Item", + "Class", + "File", + "_fillfuncargs", +] + + +class FakeCollectModule(ModuleType): + def __init__(self) -> None: + super().__init__("pytest.collect") + self.__all__ = list(COLLECT_FAKEMODULE_ATTRIBUTES) + self.__pytest = pytest + + def __dir__(self) -> List[str]: + return dir(super()) + self.__all__ + + def __getattr__(self, name: str) -> Any: + if name not in self.__all__: + raise AttributeError(name) + warnings.warn(PYTEST_COLLECT_MODULE.format(name=name), stacklevel=2) + return getattr(pytest, name) + + +sys.modules["pytest.collect"] = FakeCollectModule() diff --git a/tests/wpt/web-platform-tests/common/security-features/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/py.typed index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/common/security-features/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/src/pytest/py.typed diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py index 9b33930a06c..039d8dad969 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/acceptance_test.py @@ -1,22 +1,15 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import sys -import textwrap import types import attr import py -import six import pytest from _pytest.compat import importlib_metadata -from _pytest.main import EXIT_NOTESTSCOLLECTED -from _pytest.main import EXIT_USAGEERROR -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG +from _pytest.config import ExitCode +from _pytest.pathlib import symlink_or_skip +from _pytest.pytester import Testdir def prepend_pythonpath(*dirs): @@ -26,11 +19,11 @@ def prepend_pythonpath(*dirs): return os.pathsep.join(str(p) for p in dirs) -class TestGeneralUsage(object): +class TestGeneralUsage: def test_config_error(self, testdir): testdir.copy_example("conftest_usageerror/conftest.py") result = testdir.runpytest(testdir.tmpdir) - assert result.ret == EXIT_USAGEERROR + assert result.ret == ExitCode.USAGE_ERROR result.stderr.fnmatch_lines(["*ERROR: hello"]) result.stdout.fnmatch_lines(["*pytest_unconfigure_called"]) @@ -77,7 +70,7 @@ class TestGeneralUsage(object): def test_file_not_found(self, testdir): result = testdir.runpytest("asd") assert result.ret != 0 - result.stderr.fnmatch_lines(["ERROR: file not found*asd"]) + result.stderr.fnmatch_lines(["ERROR: file or directory not found: asd"]) def test_file_not_found_unconfigure_issue143(self, testdir): testdir.makeconftest( @@ -89,8 +82,8 @@ class TestGeneralUsage(object): """ ) result = testdir.runpytest("-s", "asd") - assert result.ret == 4 # EXIT_USAGEERROR - result.stderr.fnmatch_lines(["ERROR: file not found*asd"]) + assert result.ret == ExitCode.USAGE_ERROR + result.stderr.fnmatch_lines(["ERROR: file or directory not found: asd"]) result.stdout.fnmatch_lines(["*---configure", "*---unconfigure"]) def test_config_preparse_plugin_option(self, testdir): @@ -112,6 +105,8 @@ class TestGeneralUsage(object): @pytest.mark.parametrize("load_cov_early", [True, False]) def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early): + monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") + testdir.makepyfile(mytestplugin1_module="") testdir.makepyfile(mytestplugin2_module="") testdir.makepyfile(mycov_module="") @@ -120,7 +115,7 @@ class TestGeneralUsage(object): loaded = [] @attr.s - class DummyEntryPoint(object): + class DummyEntryPoint: name = attr.ib() module = attr.ib() group = "pytest11" @@ -137,7 +132,7 @@ class TestGeneralUsage(object): ] @attr.s - class DummyDist(object): + class DummyDist: entry_points = attr.ib() files = () @@ -152,7 +147,8 @@ class TestGeneralUsage(object): else: assert loaded == ["myplugin1", "myplugin2", "mycov"] - def test_assertion_magic(self, testdir): + @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"]) + def test_assertion_rewrite(self, testdir, import_mode): p = testdir.makepyfile( """ def test_this(): @@ -160,7 +156,7 @@ class TestGeneralUsage(object): assert x """ ) - result = testdir.runpytest(p) + result = testdir.runpytest(p, "--import-mode={}".format(import_mode)) result.stdout.fnmatch_lines(["> assert x", "E assert 0"]) assert result.ret == 1 @@ -176,7 +172,6 @@ class TestGeneralUsage(object): result = testdir.runpytest(p) result.stdout.fnmatch_lines( [ - # XXX on jython this fails: "> import import_fails", "ImportError while importing test module*", "*No module named *does_not_work*", ] @@ -187,14 +182,20 @@ class TestGeneralUsage(object): p1 = testdir.makepyfile("") p2 = testdir.makefile(".pyc", "123") result = testdir.runpytest(p1, p2) - assert result.ret - result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)]) + assert result.ret == ExitCode.USAGE_ERROR + result.stderr.fnmatch_lines( + [ + "ERROR: not found: {}".format(p2), + "(no name {!r} in any of [[][]])".format(str(p2)), + "", + ] + ) @pytest.mark.filterwarnings("default") - def test_better_reporting_on_conftest_load_failure(self, testdir, request): + def test_better_reporting_on_conftest_load_failure(self, testdir): """Show a user-friendly traceback on conftest import failures (#486, #3332)""" testdir.makepyfile("") - testdir.makeconftest( + conftest = testdir.makeconftest( """ def foo(): import qwerty @@ -209,36 +210,30 @@ class TestGeneralUsage(object): """ ) result = testdir.runpytest() - dirname = request.node.name + "0" exc_name = ( "ModuleNotFoundError" if sys.version_info >= (3, 6) else "ImportError" ) - result.stderr.fnmatch_lines( - [ - "ImportError while loading conftest '*{sep}{dirname}{sep}conftest.py'.".format( - dirname=dirname, sep=os.sep - ), - "conftest.py:3: in <module>", - " foo()", - "conftest.py:2: in foo", - " import qwerty", - "E {}: No module named {q}qwerty{q}".format( - exc_name, q="" if six.PY2 else "'" - ), - ] - ) + assert result.stdout.lines == [] + assert result.stderr.lines == [ + "ImportError while loading conftest '{}'.".format(conftest), + "conftest.py:3: in <module>", + " foo()", + "conftest.py:2: in foo", + " import qwerty", + "E {}: No module named 'qwerty'".format(exc_name), + ] def test_early_skip(self, testdir): testdir.mkdir("xyz") testdir.makeconftest( """ import pytest - def pytest_collect_directory(): + def pytest_collect_file(): pytest.skip("early") """ ) result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stdout.fnmatch_lines(["*1 skip*"]) def test_issue88_initial_file_multinodes(self, testdir): @@ -256,8 +251,8 @@ class TestGeneralUsage(object): """ ) result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED - assert "should not be seen" not in result.stdout.str() + assert result.ret == ExitCode.NO_TESTS_COLLECTED + result.stdout.no_fnmatch_line("*should not be seen*") assert "stderr42" not in result.stderr.str() def test_conftest_printing_shows_if_error(self, testdir): @@ -271,41 +266,18 @@ class TestGeneralUsage(object): assert result.ret != 0 assert "should be seen" in result.stdout.str() - @pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="symlink not available on this platform", - ) - def test_chdir(self, testdir): - testdir.tmpdir.join("py").mksymlinkto(py._pydir) - p = testdir.tmpdir.join("main.py") - p.write( - textwrap.dedent( - """\ - import sys, os - sys.path.insert(0, '') - import py - print(py.__file__) - print(py.__path__) - os.chdir(os.path.dirname(os.getcwd())) - print(py.log) - """ - ) - ) - result = testdir.runpython(p) - assert not result.ret - def test_issue109_sibling_conftests_not_loaded(self, testdir): sub1 = testdir.mkdir("sub1") sub2 = testdir.mkdir("sub2") sub1.join("conftest.py").write("assert 0") result = testdir.runpytest(sub2) - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED sub2.ensure("__init__.py") p = sub2.ensure("test_hello.py") result = testdir.runpytest(p) - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result = testdir.runpytest(sub1) - assert result.ret == EXIT_USAGEERROR + assert result.ret == ExitCode.USAGE_ERROR def test_directory_skipped(self, testdir): testdir.makeconftest( @@ -317,7 +289,7 @@ class TestGeneralUsage(object): ) testdir.makepyfile("def test_hello(): pass") result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stdout.fnmatch_lines(["*1 skipped*"]) def test_multiple_items_per_collector_byid(self, testdir): @@ -329,10 +301,10 @@ class TestGeneralUsage(object): pass class MyCollector(pytest.File): def collect(self): - return [MyItem(name="xyz", parent=self)] + return [MyItem.from_parent(name="xyz", parent=self)] def pytest_collect_file(path, parent): if path.basename.startswith("conftest"): - return MyCollector(path, parent) + return MyCollector.from_parent(fspath=path, parent=parent) """ ) result = testdir.runpytest(c.basename + "::" + "xyz") @@ -353,7 +325,7 @@ class TestGeneralUsage(object): """ ) p = testdir.makepyfile("""def test_func(x): pass""") - res = testdir.runpytest(p, SHOW_PYTEST_WARNINGS_ARG) + res = testdir.runpytest(p) assert res.ret == 0 res.stdout.fnmatch_lines(["*1 skipped*"]) @@ -366,9 +338,7 @@ class TestGeneralUsage(object): pass """ ) - res = testdir.runpytest( - p.basename + "::" + "test_func[1]", SHOW_PYTEST_WARNINGS_ARG - ) + res = testdir.runpytest(p.basename + "::" + "test_func[1]") assert res.ret == 0 res.stdout.fnmatch_lines(["*1 passed*"]) @@ -419,10 +389,10 @@ class TestGeneralUsage(object): def test_report_all_failed_collections_initargs(self, testdir): testdir.makeconftest( """ - from _pytest.main import EXIT_USAGEERROR + from _pytest.config import ExitCode def pytest_sessionfinish(exitstatus): - assert exitstatus == EXIT_USAGEERROR + assert exitstatus == ExitCode.USAGE_ERROR print("pytest_sessionfinish_called") """ ) @@ -430,17 +400,14 @@ class TestGeneralUsage(object): result = testdir.runpytest("test_a.py::a", "test_b.py::b") result.stderr.fnmatch_lines(["*ERROR*test_a.py::a*", "*ERROR*test_b.py::b*"]) result.stdout.fnmatch_lines(["pytest_sessionfinish_called"]) - assert result.ret == EXIT_USAGEERROR + assert result.ret == ExitCode.USAGE_ERROR - @pytest.mark.usefixtures("recwarn") def test_namespace_import_doesnt_confuse_import_hook(self, testdir): - """ - Ref #383. Python 3.3's namespace package messed with our import hooks + """Ref #383. + + Python 3.3's namespace package messed with our import hooks. Importing a module that didn't exist, even if the ImportError was gracefully handled, would make our test crash. - - Use recwarn here to silence this warning in Python 2.7: - ImportWarning: Not importing directory '...\not_a_package': missing __init__.py """ testdir.mkdir("not_a_package") p = testdir.makepyfile( @@ -471,7 +438,7 @@ class TestGeneralUsage(object): p = testdir.makepyfile( """ def raise_error(obj): - raise IOError('source code not available') + raise OSError('source code not available') import inspect inspect.getsourcelines = raise_error @@ -486,10 +453,8 @@ class TestGeneralUsage(object): ) def test_plugins_given_as_strings(self, tmpdir, monkeypatch, _sys_snapshot): - """test that str values passed to main() as `plugins` arg - are interpreted as module names to be imported and registered. - #855. - """ + """Test that str values passed to main() as `plugins` arg are + interpreted as module names to be imported and registered (#855).""" with pytest.raises(ImportError) as excinfo: pytest.main([str(tmpdir)], plugins=["invalid.module"]) assert "invalid" in str(excinfo.value) @@ -516,20 +481,19 @@ class TestGeneralUsage(object): def test_parametrized_with_null_bytes(self, testdir): """Test parametrization with values that contain null bytes and unicode characters (#2644, #2957)""" p = testdir.makepyfile( - u""" - # encoding: UTF-8 + """\ import pytest - @pytest.mark.parametrize("data", [b"\\x00", "\\x00", u'ação']) + @pytest.mark.parametrize("data", [b"\\x00", "\\x00", 'ação']) def test_foo(data): assert data - """ + """ ) res = testdir.runpytest(p) res.assert_outcomes(passed=3) -class TestInvocationVariants(object): +class TestInvocationVariants: def test_earlyinit(self, testdir): p = testdir.makepyfile( """ @@ -540,7 +504,6 @@ class TestInvocationVariants(object): result = testdir.runpython(p) assert result.ret == 0 - @pytest.mark.xfail("sys.platform.startswith('java')") def test_pydoc(self, testdir): for name in ("py.test", "pytest"): result = testdir.runpython_c("import {};help({})".format(name, name)) @@ -612,22 +575,23 @@ class TestInvocationVariants(object): assert res.ret == 0 res.stdout.fnmatch_lines(["*1 passed*"]) - def test_equivalence_pytest_pytest(self): - assert pytest.main == py.test.cmdline.main + def test_equivalence_pytest_pydottest(self) -> None: + # Type ignored because `py.test` is not and will not be typed. + assert pytest.main == py.test.cmdline.main # type: ignore[attr-defined] - def test_invoke_with_invalid_type(self, capsys): + def test_invoke_with_invalid_type(self) -> None: with pytest.raises( - TypeError, match="expected to be a list or tuple of strings, got: '-h'" + TypeError, match="expected to be a list of strings, got: '-h'" ): - pytest.main("-h") + pytest.main("-h") # type: ignore[arg-type] - def test_invoke_with_path(self, tmpdir, capsys): + def test_invoke_with_path(self, tmpdir: py.path.local, capsys) -> None: retcode = pytest.main(tmpdir) - assert retcode == EXIT_NOTESTSCOLLECTED + assert retcode == ExitCode.NO_TESTS_COLLECTED out, err = capsys.readouterr() - def test_invoke_plugin_api(self, testdir, capsys): - class MyPlugin(object): + def test_invoke_plugin_api(self, capsys): + class MyPlugin: def pytest_addoption(self, parser): parser.addoption("--myopt") @@ -643,7 +607,26 @@ class TestInvocationVariants(object): result = testdir.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True) assert result.ret != 0 - result.stdout.fnmatch_lines(["collected*0*items*/*1*errors"]) + result.stdout.fnmatch_lines(["collected*0*items*/*1*error"]) + + def test_pyargs_only_imported_once(self, testdir): + pkg = testdir.mkpydir("foo") + pkg.join("test_foo.py").write("print('hello from test_foo')\ndef test(): pass") + pkg.join("conftest.py").write( + "def pytest_configure(config): print('configuring')" + ) + + result = testdir.runpytest("--pyargs", "foo.test_foo", "-s", syspathinsert=True) + # should only import once + assert result.outlines.count("hello from test_foo") == 1 + # should only configure once + assert result.outlines.count("configuring") == 1 + + def test_pyargs_filename_looks_like_module(self, testdir): + testdir.tmpdir.join("conftest.py").ensure() + testdir.tmpdir.join("t.py").write("def test(): pass") + result = testdir.runpytest("--pyargs", "t.py") + assert result.ret == ExitCode.OK def test_cmdline_python_package(self, testdir, monkeypatch): import warnings @@ -675,8 +658,7 @@ class TestInvocationVariants(object): result.stderr.fnmatch_lines(["*not*found*test_missing*"]) def test_cmdline_python_namespace_package(self, testdir, monkeypatch): - """ - test --pyargs option with namespace packages (#1567) + """Test --pyargs option with namespace packages (#1567). Ref: https://packaging.python.org/guides/packaging-namespace-packages/ """ @@ -750,22 +732,11 @@ class TestInvocationVariants(object): result = testdir.runpytest(str(p) + "::test", "--doctest-modules") result.stdout.fnmatch_lines(["*1 passed*"]) - @pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires symlinks") def test_cmdline_python_package_symlink(self, testdir, monkeypatch): """ - test --pyargs option with packages with path containing symlink can - have conftest.py in their package (#2985) + --pyargs with packages with path containing symlink can have conftest.py in + their package (#2985) """ - # dummy check that we can actually create symlinks: on Windows `os.symlink` is available, - # but normal users require special admin privileges to create symlinks. - if sys.platform == "win32": - try: - os.symlink( - str(testdir.tmpdir.ensure("tmpfile")), - str(testdir.tmpdir.join("tmpfile2")), - ) - except OSError as e: - pytest.skip(six.text_type(e.args[0])) monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) dirname = "lib" @@ -781,16 +752,13 @@ class TestInvocationVariants(object): "import pytest\n@pytest.fixture\ndef a_fixture():pass" ) - d_local = testdir.mkdir("local") - symlink_location = os.path.join(str(d_local), "lib") - if six.PY2: - os.symlink(str(d), symlink_location) - else: - os.symlink(str(d), symlink_location, target_is_directory=True) + d_local = testdir.mkdir("symlink_root") + symlink_location = d_local / "lib" + symlink_or_skip(d, symlink_location, target_is_directory=True) # The structure of the test directory is now: # . - # ├── local + # ├── symlink_root # │ └── lib -> ../lib # └── lib # └── foo @@ -801,37 +769,28 @@ class TestInvocationVariants(object): # └── test_bar.py # NOTE: the different/reversed ordering is intentional here. - search_path = ["lib", os.path.join("local", "lib")] + search_path = ["lib", os.path.join("symlink_root", "lib")] monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path)) for p in search_path: monkeypatch.syspath_prepend(p) # module picked up in symlink-ed directory: - # It picks up local/lib/foo/bar (symlink) via sys.path. + # It picks up symlink_root/lib/foo/bar (symlink) via sys.path. result = testdir.runpytest("--pyargs", "-v", "foo.bar") testdir.chdir() assert result.ret == 0 - if hasattr(py.path.local, "mksymlinkto"): - result.stdout.fnmatch_lines( - [ - "lib/foo/bar/test_bar.py::test_bar PASSED*", - "lib/foo/bar/test_bar.py::test_other PASSED*", - "*2 passed*", - ] - ) - else: - result.stdout.fnmatch_lines( - [ - "*lib/foo/bar/test_bar.py::test_bar PASSED*", - "*lib/foo/bar/test_bar.py::test_other PASSED*", - "*2 passed*", - ] - ) + result.stdout.fnmatch_lines( + [ + "symlink_root/lib/foo/bar/test_bar.py::test_bar PASSED*", + "symlink_root/lib/foo/bar/test_bar.py::test_other PASSED*", + "*2 passed*", + ] + ) def test_cmdline_python_package_not_exists(self, testdir): result = testdir.runpytest("--pyargs", "tpkgwhatv") assert result.ret - result.stderr.fnmatch_lines(["ERROR*file*or*package*not*found*"]) + result.stderr.fnmatch_lines(["ERROR*module*or*package*not*found*"]) @pytest.mark.xfail(reason="decide: feature or bug") def test_noclass_discovery_if_not_testcase(self, testdir): @@ -858,16 +817,21 @@ class TestInvocationVariants(object): 4 """, ) - result = testdir.runpytest("-rf") - lines = result.stdout.str().splitlines() - for line in lines: - if line.startswith(("FAIL ", "FAILED ")): - _fail, _sep, testid = line.partition(" ") - break - result = testdir.runpytest(testid, "-rf") - result.stdout.fnmatch_lines( - ["FAILED test_doctest_id.txt::test_doctest_id.txt", "*1 failed*"] - ) + testid = "test_doctest_id.txt::test_doctest_id.txt" + expected_lines = [ + "*= FAILURES =*", + "*_ ?doctest? test_doctest_id.txt _*", + "FAILED test_doctest_id.txt::test_doctest_id.txt", + "*= 1 failed in*", + ] + result = testdir.runpytest(testid, "-rf", "--tb=short") + result.stdout.fnmatch_lines(expected_lines) + + # Ensure that re-running it will still handle it as + # doctest.DocTestFailure, which was not the case before when + # re-importing doctest, but not creating a new RUNNER_CLASS. + result = testdir.runpytest(testid, "-rf", "--tb=short") + result.stdout.fnmatch_lines(expected_lines) def test_core_backward_compatibility(self): """Test backward compatibility for get_plugin_manager function. See #787.""" @@ -883,43 +847,48 @@ class TestInvocationVariants(object): assert request.config.pluginmanager.hasplugin("python") -class TestDurations(object): +class TestDurations: source = """ - import time - frag = 0.002 + from _pytest import timing def test_something(): pass def test_2(): - time.sleep(frag*5) + timing.sleep(0.010) def test_1(): - time.sleep(frag) + timing.sleep(0.002) def test_3(): - time.sleep(frag*10) + timing.sleep(0.020) """ - def test_calls(self, testdir): + def test_calls(self, testdir, mock_timing): testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=10") + result = testdir.runpytest_inprocess("--durations=10") assert result.ret == 0 + result.stdout.fnmatch_lines_random( ["*durations*", "*call*test_3*", "*call*test_2*"] ) + result.stdout.fnmatch_lines( - ["(0.00 durations hidden. Use -vv to show these durations.)"] + ["(8 durations < 0.005s hidden. Use -vv to show these durations.)"] ) - def test_calls_show_2(self, testdir): + def test_calls_show_2(self, testdir, mock_timing): + testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=2") + result = testdir.runpytest_inprocess("--durations=2") assert result.ret == 0 + lines = result.stdout.get_lines_after("*slowest*durations*") assert "4 passed" in lines[2] - def test_calls_showall(self, testdir): + def test_calls_showall(self, testdir, mock_timing): testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=0") + result = testdir.runpytest_inprocess("--durations=0") assert result.ret == 0 - for x in "23": + + tested = "3" + for x in tested: for y in ("call",): # 'setup', 'call', 'teardown': for line in result.stdout.lines: if ("test_%s" % x) in line and y in line: @@ -927,10 +896,11 @@ class TestDurations(object): else: raise AssertionError("not found {} {}".format(x, y)) - def test_calls_showall_verbose(self, testdir): + def test_calls_showall_verbose(self, testdir, mock_timing): testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=0", "-vv") + result = testdir.runpytest_inprocess("--durations=0", "-vv") assert result.ret == 0 + for x in "123": for y in ("call",): # 'setup', 'call', 'teardown': for line in result.stdout.lines: @@ -939,52 +909,53 @@ class TestDurations(object): else: raise AssertionError("not found {} {}".format(x, y)) - def test_with_deselected(self, testdir): + def test_with_deselected(self, testdir, mock_timing): testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=2", "-k test_2") + result = testdir.runpytest_inprocess("--durations=2", "-k test_3") assert result.ret == 0 - result.stdout.fnmatch_lines(["*durations*", "*call*test_2*"]) - def test_with_failing_collection(self, testdir): + result.stdout.fnmatch_lines(["*durations*", "*call*test_3*"]) + + def test_with_failing_collection(self, testdir, mock_timing): testdir.makepyfile(self.source) testdir.makepyfile(test_collecterror="""xyz""") - result = testdir.runpytest("--durations=2", "-k test_1") + result = testdir.runpytest_inprocess("--durations=2", "-k test_1") assert result.ret == 2 - result.stdout.fnmatch_lines(["*Interrupted: 1 errors during collection*"]) + + result.stdout.fnmatch_lines(["*Interrupted: 1 error during collection*"]) # Collection errors abort test execution, therefore no duration is # output - assert "duration" not in result.stdout.str() + result.stdout.no_fnmatch_line("*duration*") - def test_with_not(self, testdir): + def test_with_not(self, testdir, mock_timing): testdir.makepyfile(self.source) - result = testdir.runpytest("-k not 1") + result = testdir.runpytest_inprocess("-k not 1") assert result.ret == 0 -class TestDurationWithFixture(object): +class TestDurationsWithFixture: source = """ import pytest - import time - frag = 0.01 + from _pytest import timing @pytest.fixture def setup_fixt(): - time.sleep(frag) + timing.sleep(2) def test_1(setup_fixt): - time.sleep(frag) + timing.sleep(5) """ - def test_setup_function(self, testdir): + def test_setup_function(self, testdir, mock_timing): testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=10") + result = testdir.runpytest_inprocess("--durations=10") assert result.ret == 0 result.stdout.fnmatch_lines_random( """ *durations* - * setup *test_1* - * call *test_1* + 5.00s call *test_1* + 2.00s setup *test_1* """ ) @@ -998,7 +969,7 @@ def test_zipimport_hook(testdir, tmpdir): "app/foo.py": """ import pytest def main(): - pytest.main(['--pyarg', 'foo']) + pytest.main(['--pyargs', 'foo']) """ } ) @@ -1007,21 +978,13 @@ def test_zipimport_hook(testdir, tmpdir): result = testdir.runpython(target) assert result.ret == 0 result.stderr.fnmatch_lines(["*not found*foo*"]) - assert "INTERNALERROR>" not in result.stdout.str() + result.stdout.no_fnmatch_line("*INTERNALERROR>*") def test_import_plugin_unicode_name(testdir): testdir.makepyfile(myplugin="") - testdir.makepyfile( - """ - def test(): pass - """ - ) - testdir.makeconftest( - """ - pytest_plugins = [u'myplugin'] - """ - ) + testdir.makepyfile("def test(): pass") + testdir.makeconftest("pytest_plugins = ['myplugin']") r = testdir.runpytest() assert r.ret == 0 @@ -1041,9 +1004,7 @@ def test_pytest_plugins_as_module(testdir): def test_deferred_hook_checking(testdir): - """ - Check hooks as late as possible (#1821). - """ + """Check hooks as late as possible (#1821).""" testdir.syspathinsert() testdir.makepyfile( **{ @@ -1111,13 +1072,15 @@ def test_fixture_values_leak(testdir): assert fix_of_test1_ref() is None """ ) - result = testdir.runpytest() + # Running on subprocess does not activate the HookRecorder + # which holds itself a reference to objects in case of the + # pytest_assert_reprcompare hook + result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines(["* 2 passed *"]) def test_fixture_order_respects_scope(testdir): - """Ensure that fixtures are created according to scope order, regression test for #2405 - """ + """Ensure that fixtures are created according to scope order (#2405).""" testdir.makepyfile( """ import pytest @@ -1142,7 +1105,8 @@ def test_fixture_order_respects_scope(testdir): def test_frame_leak_on_failing_test(testdir): - """pytest would leak garbage referencing the frames of tests that failed that could never be reclaimed (#2798) + """Pytest would leak garbage referencing the frames of tests that failed + that could never be reclaimed (#2798). Unfortunately it was not possible to remove the actual circles because most of them are made of traceback objects which cannot be weakly referenced. Those objects at least @@ -1182,20 +1146,55 @@ def test_fixture_mock_integration(testdir): def test_usage_error_code(testdir): result = testdir.runpytest("-unknown-option-") - assert result.ret == EXIT_USAGEERROR + assert result.ret == ExitCode.USAGE_ERROR -@pytest.mark.skipif( - sys.version_info[:2] < (3, 5), reason="async def syntax python 3.5+ only" -) @pytest.mark.filterwarnings("default") def test_warn_on_async_function(testdir): + # In the below we .close() the coroutine only to avoid + # "RuntimeWarning: coroutine 'test_2' was never awaited" + # which messes with other tests. testdir.makepyfile( test_async=""" async def test_1(): pass async def test_2(): pass + def test_3(): + coro = test_2() + coro.close() + return coro + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "test_async.py::test_1", + "test_async.py::test_2", + "test_async.py::test_3", + "*async def functions are not natively supported*", + "*3 skipped, 3 warnings in*", + ] + ) + # ensure our warning message appears only once + assert ( + result.stdout.str().count("async def functions are not natively supported") == 1 + ) + + +@pytest.mark.filterwarnings("default") +@pytest.mark.skipif( + sys.version_info < (3, 6), reason="async gen syntax available in Python 3.6+" +) +def test_warn_on_async_gen_function(testdir): + testdir.makepyfile( + test_async=""" + async def test_1(): + yield + async def test_2(): + yield + def test_3(): + return test_2() """ ) result = testdir.runpytest() @@ -1203,11 +1202,95 @@ def test_warn_on_async_function(testdir): [ "test_async.py::test_1", "test_async.py::test_2", - "*Coroutine functions are not natively supported*", - "*2 skipped, 2 warnings in*", + "test_async.py::test_3", + "*async def functions are not natively supported*", + "*3 skipped, 3 warnings in*", ] ) # ensure our warning message appears only once assert ( - result.stdout.str().count("Coroutine functions are not natively supported") == 1 + result.stdout.str().count("async def functions are not natively supported") == 1 + ) + + +def test_pdb_can_be_rewritten(testdir): + testdir.makepyfile( + **{ + "conftest.py": """ + import pytest + pytest.register_assert_rewrite("pdb") + """, + "__init__.py": "", + "pdb.py": """ + def check(): + assert 1 == 2 + """, + "test_pdb.py": """ + def test(): + import pdb + assert pdb.check() + """, + } + ) + # Disable debugging plugin itself to avoid: + # > INTERNALERROR> AttributeError: module 'pdb' has no attribute 'set_trace' + result = testdir.runpytest_subprocess("-p", "no:debugging", "-vv") + result.stdout.fnmatch_lines( + [ + " def check():", + "> assert 1 == 2", + "E assert 1 == 2", + "E +1", + "E -2", + "", + "pdb.py:2: AssertionError", + "*= 1 failed in *", + ] ) + assert result.ret == 1 + + +def test_tee_stdio_captures_and_live_prints(testdir): + testpath = testdir.makepyfile( + """ + import sys + def test_simple(): + print ("@this is stdout@") + print ("@this is stderr@", file=sys.stderr) + """ + ) + result = testdir.runpytest_subprocess( + testpath, + "--capture=tee-sys", + "--junitxml=output.xml", + "-o", + "junit_logging=all", + ) + + # ensure stdout/stderr were 'live printed' + result.stdout.fnmatch_lines(["*@this is stdout@*"]) + result.stderr.fnmatch_lines(["*@this is stderr@*"]) + + # now ensure the output is in the junitxml + with open(os.path.join(testdir.tmpdir.strpath, "output.xml")) as f: + fullXml = f.read() + assert "@this is stdout@\n" in fullXml + assert "@this is stderr@\n" in fullXml + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="Windows raises `OSError: [Errno 22] Invalid argument` instead", +) +def test_no_brokenpipeerror_message(testdir: Testdir) -> None: + """Ensure that the broken pipe error message is supressed. + + In some Python versions, it reaches sys.unraisablehook, in others + a BrokenPipeError exception is propagated, but either way it prints + to stderr on shutdown, so checking nothing is printed is enough. + """ + popen = testdir.popen((*testdir._getpytestargs(), "--help")) + popen.stdout.close() + ret = popen.wait() + assert popen.stderr.read() == b"" + assert ret == 1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py index 0b63fcf4d42..bae86be347f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_code.py @@ -1,211 +1,211 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import re import sys +from types import FrameType +from unittest import mock -from six import text_type -from test_excinfo import TWMock - -import _pytest._code import pytest - -try: - import mock -except ImportError: - import unittest.mock as mock +from _pytest._code import Code +from _pytest._code import ExceptionInfo +from _pytest._code import Frame +from _pytest._code import Source +from _pytest._code.code import ExceptionChainRepr +from _pytest._code.code import ReprFuncArgs -def test_ne(): - code1 = _pytest._code.Code(compile('foo = "bar"', "", "exec")) +def test_ne() -> None: + code1 = Code(compile('foo = "bar"', "", "exec")) assert code1 == code1 - code2 = _pytest._code.Code(compile('foo = "baz"', "", "exec")) + code2 = Code(compile('foo = "baz"', "", "exec")) assert code2 != code1 -def test_code_gives_back_name_for_not_existing_file(): +def test_code_gives_back_name_for_not_existing_file() -> None: name = "abc-123" co_code = compile("pass\n", name, "exec") assert co_code.co_filename == name - code = _pytest._code.Code(co_code) + code = Code(co_code) assert str(code.path) == name assert code.fullsource is None -def test_code_with_class(): - class A(object): +def test_code_with_class() -> None: + class A: pass - pytest.raises(TypeError, _pytest._code.Code, A) + pytest.raises(TypeError, Code, A) -def x(): +def x() -> None: raise NotImplementedError() -def test_code_fullsource(): - code = _pytest._code.Code(x) +def test_code_fullsource() -> None: + code = Code(x) full = code.fullsource assert "test_code_fullsource()" in str(full) -def test_code_source(): - code = _pytest._code.Code(x) +def test_code_source() -> None: + code = Code(x) src = code.source() - expected = """def x(): + expected = """def x() -> None: raise NotImplementedError()""" assert str(src) == expected -def test_frame_getsourcelineno_myself(): - def func(): +def test_frame_getsourcelineno_myself() -> None: + def func() -> FrameType: return sys._getframe(0) - f = func() - f = _pytest._code.Frame(f) + f = Frame(func()) source, lineno = f.code.fullsource, f.lineno + assert source is not None assert source[lineno].startswith(" return sys._getframe(0)") -def test_getstatement_empty_fullsource(): - def func(): +def test_getstatement_empty_fullsource() -> None: + def func() -> FrameType: return sys._getframe(0) - f = func() - f = _pytest._code.Frame(f) + f = Frame(func()) with mock.patch.object(f.code.__class__, "fullsource", None): - assert f.statement == "" + assert f.statement == Source("") -def test_code_from_func(): - co = _pytest._code.Code(test_frame_getsourcelineno_myself) +def test_code_from_func() -> None: + co = Code(test_frame_getsourcelineno_myself) assert co.firstlineno assert co.path -def test_unicode_handling(): - value = u"ąć".encode("UTF-8") +def test_unicode_handling() -> None: + value = "ąć".encode() - def f(): + def f() -> None: raise Exception(value) excinfo = pytest.raises(Exception, f) - text_type(excinfo) - if sys.version_info < (3,): - bytes(excinfo) - - -@pytest.mark.skipif(sys.version_info[0] >= 3, reason="python 2 only issue") -def test_unicode_handling_syntax_error(): - value = u"ąć".encode("UTF-8") - - def f(): - raise SyntaxError("invalid syntax", (None, 1, 3, value)) - - excinfo = pytest.raises(Exception, f) str(excinfo) - if sys.version_info[0] < 3: - text_type(excinfo) -def test_code_getargs(): +def test_code_getargs() -> None: def f1(x): raise NotImplementedError() - c1 = _pytest._code.Code(f1) + c1 = Code(f1) assert c1.getargs(var=True) == ("x",) def f2(x, *y): raise NotImplementedError() - c2 = _pytest._code.Code(f2) + c2 = Code(f2) assert c2.getargs(var=True) == ("x", "y") def f3(x, **z): raise NotImplementedError() - c3 = _pytest._code.Code(f3) + c3 = Code(f3) assert c3.getargs(var=True) == ("x", "z") def f4(x, *y, **z): raise NotImplementedError() - c4 = _pytest._code.Code(f4) + c4 = Code(f4) assert c4.getargs(var=True) == ("x", "y", "z") -def test_frame_getargs(): - def f1(x): +def test_frame_getargs() -> None: + def f1(x) -> FrameType: return sys._getframe(0) - fr1 = _pytest._code.Frame(f1("a")) + fr1 = Frame(f1("a")) assert fr1.getargs(var=True) == [("x", "a")] - def f2(x, *y): + def f2(x, *y) -> FrameType: return sys._getframe(0) - fr2 = _pytest._code.Frame(f2("a", "b", "c")) + fr2 = Frame(f2("a", "b", "c")) assert fr2.getargs(var=True) == [("x", "a"), ("y", ("b", "c"))] - def f3(x, **z): + def f3(x, **z) -> FrameType: return sys._getframe(0) - fr3 = _pytest._code.Frame(f3("a", b="c")) + fr3 = Frame(f3("a", b="c")) assert fr3.getargs(var=True) == [("x", "a"), ("z", {"b": "c"})] - def f4(x, *y, **z): + def f4(x, *y, **z) -> FrameType: return sys._getframe(0) - fr4 = _pytest._code.Frame(f4("a", "b", c="d")) + fr4 = Frame(f4("a", "b", c="d")) assert fr4.getargs(var=True) == [("x", "a"), ("y", ("b",)), ("z", {"c": "d"})] -class TestExceptionInfo(object): - def test_bad_getsource(self): +class TestExceptionInfo: + def test_bad_getsource(self) -> None: try: if False: pass else: assert False except AssertionError: - exci = _pytest._code.ExceptionInfo.from_current() + exci = ExceptionInfo.from_current() assert exci.getrepr() - def test_from_current_with_missing(self): + def test_from_current_with_missing(self) -> None: with pytest.raises(AssertionError, match="no current exception"): - _pytest._code.ExceptionInfo.from_current() + ExceptionInfo.from_current() -class TestTracebackEntry(object): - def test_getsource(self): +class TestTracebackEntry: + def test_getsource(self) -> None: try: if False: pass else: assert False except AssertionError: - exci = _pytest._code.ExceptionInfo.from_current() + exci = ExceptionInfo.from_current() entry = exci.traceback[0] source = entry.getsource() + assert source is not None assert len(source) == 6 assert "assert False" in source[5] + def test_tb_entry_str(self): + try: + assert False + except AssertionError: + exci = ExceptionInfo.from_current() + pattern = r" File '.*test_code.py':\d+ in test_tb_entry_str\n assert False" + entry = str(exci.traceback[0]) + assert re.match(pattern, entry) -class TestReprFuncArgs(object): - def test_not_raise_exception_with_mixed_encoding(self): - from _pytest._code.code import ReprFuncArgs - - tw = TWMock() - args = [("unicode_string", u"São Paulo"), ("utf8_string", b"S\xc3\xa3o Paulo")] +class TestReprFuncArgs: + def test_not_raise_exception_with_mixed_encoding(self, tw_mock) -> None: + args = [("unicode_string", "São Paulo"), ("utf8_string", b"S\xc3\xa3o Paulo")] r = ReprFuncArgs(args) - r.toterminal(tw) - if sys.version_info[0] >= 3: - assert ( - tw.lines[0] - == r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'" - ) - else: - assert tw.lines[0] == "unicode_string = São Paulo, utf8_string = São Paulo" + r.toterminal(tw_mock) + + assert ( + tw_mock.lines[0] + == r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'" + ) + + +def test_ExceptionChainRepr(): + """Test ExceptionChainRepr, especially with regard to being hashable.""" + try: + raise ValueError() + except ValueError: + excinfo1 = ExceptionInfo.from_current() + excinfo2 = ExceptionInfo.from_current() + + repr1 = excinfo1.getrepr() + repr2 = excinfo2.getrepr() + assert repr1 != repr2 + + assert isinstance(repr1, ExceptionChainRepr) + assert hash(repr1) != hash(repr2) + assert repr1 is not excinfo1.getrepr() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py index a76797301dd..5754977ddc7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_excinfo.py @@ -1,23 +1,24 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import io import operator import os +import queue import sys import textwrap +from typing import Any +from typing import Dict +from typing import Tuple +from typing import Union import py -import six -from six.moves import queue import _pytest import pytest from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import FormattedExcinfo -from _pytest._code.code import ReprExceptionInfo +from _pytest._io import TerminalWriter +from _pytest.compat import TYPE_CHECKING +from _pytest.pytester import LineMatcher try: import importlib @@ -26,9 +27,8 @@ except ImportError: else: invalidate_import_caches = getattr(importlib, "invalidate_caches", None) -failsonjython = pytest.mark.xfail("sys.platform.startswith('java')") - -pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3])) +if TYPE_CHECKING: + from _pytest._code.code import _TracebackStyle @pytest.fixture @@ -39,34 +39,7 @@ def limited_recursion_depth(): sys.setrecursionlimit(before) -class TWMock(object): - WRITE = object() - - def __init__(self): - self.lines = [] - self.is_writing = False - - def sep(self, sep, line=None): - self.lines.append((sep, line)) - - def write(self, msg, **kw): - self.lines.append((TWMock.WRITE, msg)) - - def line(self, line, **kw): - self.lines.append(line) - - def markup(self, text, **kw): - return text - - def get_write_msg(self, idx): - flag, msg = self.lines[idx] - assert flag == TWMock.WRITE - return msg - - fullwidth = 80 - - -def test_excinfo_simple(): +def test_excinfo_simple() -> None: try: raise ValueError except ValueError: @@ -74,6 +47,15 @@ def test_excinfo_simple(): assert info.type == ValueError +def test_excinfo_from_exc_info_simple() -> None: + try: + raise ValueError + except ValueError as e: + assert e.__traceback__ is not None + info = _pytest._code.ExceptionInfo.from_exc_info((type(e), e, e.__traceback__)) + assert info.type == ValueError + + def test_excinfo_getstatement(): def g(): raise ValueError @@ -86,9 +68,9 @@ def test_excinfo_getstatement(): except ValueError: excinfo = _pytest._code.ExceptionInfo.from_current() linenumbers = [ - _pytest._code.getrawcode(f).co_firstlineno - 1 + 4, - _pytest._code.getrawcode(f).co_firstlineno - 1 + 1, - _pytest._code.getrawcode(g).co_firstlineno - 1 + 1, + f.__code__.co_firstlineno - 1 + 4, + f.__code__.co_firstlineno - 1 + 1, + g.__code__.co_firstlineno - 1 + 1, ] values = list(excinfo.traceback) foundlinenumbers = [x.lineno for x in values] @@ -120,7 +102,7 @@ def h(): # -class TestTraceback_f_g_h(object): +class TestTraceback_f_g_h: def setup_method(self, method): try: h() @@ -146,26 +128,29 @@ class TestTraceback_f_g_h(object): assert s.startswith("def f():") assert s.endswith("raise ValueError") - @failsonjython def test_traceback_entry_getsource_in_construct(self): - source = _pytest._code.Source( - """\ - def xyz(): - try: - raise ValueError - except somenoname: - pass - xyz() - """ - ) + def xyz(): + try: + raise ValueError + except somenoname: # type: ignore[name-defined] # noqa: F821 + pass # pragma: no cover + try: - exec(source.compile()) + xyz() except NameError: - tb = _pytest._code.ExceptionInfo.from_current().traceback - print(tb[-1].getsource()) - s = str(tb[-1].getsource()) - assert s.startswith("def xyz():\n try:") - assert s.strip().endswith("except somenoname:") + excinfo = _pytest._code.ExceptionInfo.from_current() + else: + assert False, "did not raise NameError" + + tb = excinfo.traceback + source = tb[-1].getsource() + assert source is not None + assert source.deindent().lines == [ + "def xyz():", + " try:", + " raise ValueError", + " except somenoname: # type: ignore[name-defined] # noqa: F821", + ] def test_traceback_cut(self): co = _pytest._code.Code(f) @@ -252,23 +237,25 @@ class TestTraceback_f_g_h(object): repr = excinfo.getrepr() assert "RuntimeError: hello" in str(repr.reprcrash) - def test_traceback_no_recursion_index(self): - def do_stuff(): + def test_traceback_no_recursion_index(self) -> None: + def do_stuff() -> None: raise RuntimeError - def reraise_me(): + def reraise_me() -> None: import sys exc, val, tb = sys.exc_info() - six.reraise(exc, val, tb) + assert val is not None + raise val.with_traceback(tb) - def f(n): + def f(n: int) -> None: try: do_stuff() - except: # noqa + except BaseException: reraise_me() excinfo = pytest.raises(RuntimeError, f, 8) + assert excinfo is not None traceback = excinfo.traceback recindex = traceback.recursionindex() assert recindex is None @@ -342,22 +329,25 @@ def test_excinfo_exconly(): assert msg.endswith("world") -def test_excinfo_repr(): - excinfo = pytest.raises(ValueError, h) - s = repr(excinfo) - assert s == "<ExceptionInfo ValueError tblen=4>" +def test_excinfo_repr_str() -> None: + excinfo1 = pytest.raises(ValueError, h) + assert repr(excinfo1) == "<ExceptionInfo ValueError() tblen=4>" + assert str(excinfo1) == "<ExceptionInfo ValueError() tblen=4>" + class CustomException(Exception): + def __repr__(self): + return "custom_repr" -def test_excinfo_str(): - excinfo = pytest.raises(ValueError, h) - s = str(excinfo) - assert s.startswith(__file__[:-9]) # pyc file and $py.class - assert s.endswith("ValueError") - assert len(s.split(":")) >= 3 # on windows it's 4 + def raises() -> None: + raise CustomException() + + excinfo2 = pytest.raises(CustomException, raises) + assert repr(excinfo2) == "<ExceptionInfo custom_repr tblen=2>" + assert str(excinfo2) == "<ExceptionInfo custom_repr tblen=2>" -def test_excinfo_for_later(): - e = ExceptionInfo.for_later() +def test_excinfo_for_later() -> None: + e = ExceptionInfo[BaseException].for_later() assert "for raises" in repr(e) assert "for raises" in str(e) @@ -387,8 +377,8 @@ def test_excinfo_no_python_sourcecode(tmpdir): excinfo = pytest.raises(ValueError, template.render, h=h) for item in excinfo.traceback: print(item) # XXX: for some reason jinja.Template.render is printed in full - item.source # shouldnt fail - if item.path.basename == "test.txt": + item.source # shouldn't fail + if isinstance(item.path, py.path.local) and item.path.basename == "test.txt": assert str(item.source) == "{{ h()}}:" @@ -434,10 +424,19 @@ def test_match_raises_error(testdir): ) result = testdir.runpytest() assert result.ret != 0 - result.stdout.fnmatch_lines(["*AssertionError*Pattern*[123]*not found*"]) + + exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." + result.stdout.fnmatch_lines(["E * AssertionError: {}".format(exc_msg)]) + result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") + + result = testdir.runpytest("--fulltrace") + assert result.ret != 0 + result.stdout.fnmatch_lines( + ["*__tracebackhide__ = True*", "E * AssertionError: {}".format(exc_msg)] + ) -class TestFormattedExcinfo(object): +class TestFormattedExcinfo: @pytest.fixture def importasmod(self, request, _sys_snapshot): def importasmod(source): @@ -452,16 +451,6 @@ class TestFormattedExcinfo(object): return importasmod - def excinfo_from_exec(self, source): - source = _pytest._code.Source(source).strip() - try: - exec(source.compile()) - except KeyboardInterrupt: - raise - except: # noqa - return _pytest._code.ExceptionInfo.from_current() - assert 0, "did not raise" - def test_repr_source(self): pr = FormattedExcinfo() source = _pytest._code.Source( @@ -476,20 +465,31 @@ class TestFormattedExcinfo(object): assert lines[0] == "| def f(x):" assert lines[1] == " pass" - def test_repr_source_excinfo(self): - """ check if indentation is right """ - pr = FormattedExcinfo() - excinfo = self.excinfo_from_exec( - """ - def f(): - assert 0 - f() - """ - ) + def test_repr_source_excinfo(self) -> None: + """Check if indentation is right.""" + try: + + def f(): + 1 / 0 + + f() + + except BaseException: + excinfo = _pytest._code.ExceptionInfo.from_current() + else: + assert False, "did not raise" + pr = FormattedExcinfo() source = pr._getentrysource(excinfo.traceback[-1]) + assert source is not None lines = pr.get_source(source, 1, excinfo) - assert lines == [" def f():", "> assert 0", "E AssertionError"] + for line in lines: + print(line) + assert lines == [ + " def f():", + "> 1 / 0", + "E ZeroDivisionError: division by zero", + ] def test_repr_source_not_existing(self): pr = FormattedExcinfo() @@ -500,8 +500,7 @@ class TestFormattedExcinfo(object): excinfo = _pytest._code.ExceptionInfo.from_current() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" + assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" def test_repr_many_line_source_not_existing(self): pr = FormattedExcinfo() @@ -519,84 +518,35 @@ raise ValueError() excinfo = _pytest._code.ExceptionInfo.from_current() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" + assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" - def test_repr_source_failing_fullsource(self): + def test_repr_source_failing_fullsource(self, monkeypatch) -> None: pr = FormattedExcinfo() - class FakeCode(object): - class raw(object): - co_filename = "?" - - path = "?" - firstlineno = 5 - - def fullsource(self): - return None - - fullsource = property(fullsource) - - class FakeFrame(object): - code = FakeCode() - f_locals = {} - f_globals = {} - - class FakeTracebackEntry(_pytest._code.Traceback.Entry): - def __init__(self, tb, excinfo=None): - self.lineno = 5 + 3 - - @property - def frame(self): - return FakeFrame() - - class Traceback(_pytest._code.Traceback): - Entry = FakeTracebackEntry - - class FakeExcinfo(_pytest._code.ExceptionInfo): - typename = "Foo" - value = Exception() - - def __init__(self): - pass - - def exconly(self, tryshort): - return "EXC" - - def errisinstance(self, cls): - return False - - excinfo = FakeExcinfo() - - class FakeRawTB(object): - tb_next = None + try: + 1 / 0 + except ZeroDivisionError: + excinfo = ExceptionInfo.from_current() - tb = FakeRawTB() - excinfo.traceback = Traceback(tb) + with monkeypatch.context() as m: + m.setattr(_pytest._code.Code, "fullsource", property(lambda self: None)) + repr = pr.repr_excinfo(excinfo) - fail = IOError() - repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" + assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" - fail = py.error.ENOENT # noqa - repr = pr.repr_excinfo(excinfo) - assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" - if sys.version_info[0] >= 3: - assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" - - def test_repr_local(self): + def test_repr_local(self) -> None: p = FormattedExcinfo(showlocals=True) loc = {"y": 5, "z": 7, "x": 3, "@x": 2, "__builtins__": {}} reprlocals = p.repr_locals(loc) + assert reprlocals is not None assert reprlocals.lines assert reprlocals.lines[0] == "__builtins__ = <builtins>" assert reprlocals.lines[1] == "x = 3" assert reprlocals.lines[2] == "y = 5" assert reprlocals.lines[3] == "z = 7" - def test_repr_local_with_error(self): + def test_repr_local_with_error(self) -> None: class ObjWithErrorInRepr: def __repr__(self): raise NotImplementedError @@ -604,13 +554,15 @@ raise ValueError() p = FormattedExcinfo(showlocals=True, truncate_locals=False) loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} reprlocals = p.repr_locals(loc) + assert reprlocals is not None assert reprlocals.lines assert reprlocals.lines[0] == "__builtins__ = <builtins>" - assert '[NotImplementedError("") raised in repr()]' in reprlocals.lines[1] + assert "[NotImplementedError() raised in repr()]" in reprlocals.lines[1] - def test_repr_local_with_exception_in_class_property(self): + def test_repr_local_with_exception_in_class_property(self) -> None: class ExceptionWithBrokenClass(Exception): - @property + # Type ignored because it's bypassed intentionally. + @property # type: ignore def __class__(self): raise TypeError("boom!") @@ -621,23 +573,26 @@ raise ValueError() p = FormattedExcinfo(showlocals=True, truncate_locals=False) loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} reprlocals = p.repr_locals(loc) + assert reprlocals is not None assert reprlocals.lines assert reprlocals.lines[0] == "__builtins__ = <builtins>" - assert '[ExceptionWithBrokenClass("") raised in repr()]' in reprlocals.lines[1] + assert "[ExceptionWithBrokenClass() raised in repr()]" in reprlocals.lines[1] - def test_repr_local_truncated(self): + def test_repr_local_truncated(self) -> None: loc = {"l": [i for i in range(10)]} p = FormattedExcinfo(showlocals=True) truncated_reprlocals = p.repr_locals(loc) + assert truncated_reprlocals is not None assert truncated_reprlocals.lines assert truncated_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, ...]" q = FormattedExcinfo(showlocals=True, truncate_locals=False) full_reprlocals = q.repr_locals(loc) + assert full_reprlocals is not None assert full_reprlocals.lines assert full_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" - def test_repr_tracebackentry_lines(self, importasmod): + def test_repr_tracebackentry_lines(self, importasmod) -> None: mod = importasmod( """ def func1(): @@ -664,13 +619,13 @@ raise ValueError() assert lines[3] == "E world" assert not lines[4:] - loc = repr_entry.reprlocals is not None loc = repr_entry.reprfileloc + assert loc is not None assert loc.path == mod.__file__ assert loc.lineno == 3 # assert loc.message == "ValueError: hello" - def test_repr_tracebackentry_lines2(self, importasmod): + def test_repr_tracebackentry_lines2(self, importasmod, tw_mock) -> None: mod = importasmod( """ def func1(m, x, y, z): @@ -682,6 +637,7 @@ raise ValueError() entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) + assert reprfuncargs is not None assert reprfuncargs.args[0] == ("m", repr("m" * 90)) assert reprfuncargs.args[1] == ("x", "5") assert reprfuncargs.args[2] == ("y", "13") @@ -689,14 +645,14 @@ raise ValueError() p = FormattedExcinfo(funcargs=True) repr_entry = p.repr_traceback_entry(entry) + assert repr_entry.reprfuncargs is not None assert repr_entry.reprfuncargs.args == reprfuncargs.args - tw = TWMock() - repr_entry.toterminal(tw) - assert tw.lines[0] == "m = " + repr("m" * 90) - assert tw.lines[1] == "x = 5, y = 13" - assert tw.lines[2] == "z = " + repr("z" * 120) + repr_entry.toterminal(tw_mock) + assert tw_mock.lines[0] == "m = " + repr("m" * 90) + assert tw_mock.lines[1] == "x = 5, y = 13" + assert tw_mock.lines[2] == "z = " + repr("z" * 120) - def test_repr_tracebackentry_lines_var_kw_args(self, importasmod): + def test_repr_tracebackentry_lines_var_kw_args(self, importasmod, tw_mock) -> None: mod = importasmod( """ def func1(x, *y, **z): @@ -708,18 +664,19 @@ raise ValueError() entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) + assert reprfuncargs is not None assert reprfuncargs.args[0] == ("x", repr("a")) assert reprfuncargs.args[1] == ("y", repr(("b",))) assert reprfuncargs.args[2] == ("z", repr({"c": "d"})) p = FormattedExcinfo(funcargs=True) repr_entry = p.repr_traceback_entry(entry) + assert repr_entry.reprfuncargs assert repr_entry.reprfuncargs.args == reprfuncargs.args - tw = TWMock() - repr_entry.toterminal(tw) - assert tw.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}" + repr_entry.toterminal(tw_mock) + assert tw_mock.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}" - def test_repr_tracebackentry_short(self, importasmod): + def test_repr_tracebackentry_short(self, importasmod) -> None: mod = importasmod( """ def func1(): @@ -734,6 +691,7 @@ raise ValueError() lines = reprtb.lines basename = py.path.local(mod.__file__).basename assert lines[0] == " func1()" + assert reprtb.reprfileloc is not None assert basename in str(reprtb.reprfileloc.path) assert reprtb.reprfileloc.lineno == 5 @@ -743,6 +701,7 @@ raise ValueError() lines = reprtb.lines assert lines[0] == ' raise ValueError("hello")' assert lines[1] == "E ValueError: hello" + assert reprtb.reprfileloc is not None assert basename in str(reprtb.reprfileloc.path) assert reprtb.reprfileloc.lineno == 3 @@ -782,7 +741,7 @@ raise ValueError() reprtb = p.repr_traceback(excinfo) assert len(reprtb.reprentries) == 3 - def test_traceback_short_no_source(self, importasmod, monkeypatch): + def test_traceback_short_no_source(self, importasmod, monkeypatch) -> None: mod = importasmod( """ def func1(): @@ -795,7 +754,7 @@ raise ValueError() from _pytest._code.code import Code monkeypatch.setattr(Code, "path", "bogus") - excinfo.traceback[0].frame.code.path = "bogus" + excinfo.traceback[0].frame.code.path = "bogus" # type: ignore[misc] p = FormattedExcinfo(style="short") reprtb = p.repr_traceback_entry(excinfo.traceback[-2]) lines = reprtb.lines @@ -808,7 +767,7 @@ raise ValueError() assert last_lines[0] == ' raise ValueError("hello")' assert last_lines[1] == "E ValueError: hello" - def test_repr_traceback_and_excinfo(self, importasmod): + def test_repr_traceback_and_excinfo(self, importasmod) -> None: mod = importasmod( """ def f(x): @@ -819,7 +778,8 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.entry) - for style in ("long", "short"): + styles = ("long", "short") # type: Tuple[_TracebackStyle, ...] + for style in styles: p = FormattedExcinfo(style=style) reprtb = p.repr_traceback(excinfo) assert len(reprtb.reprentries) == 2 @@ -828,13 +788,14 @@ raise ValueError() repr = p.repr_excinfo(excinfo) assert repr.reprtraceback assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries) - if sys.version_info[0] >= 3: - assert repr.chain[0][0] - assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries) + + assert repr.chain[0][0] + assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries) + assert repr.reprcrash is not None assert repr.reprcrash.path.endswith("mod.py") assert repr.reprcrash.message == "ValueError: 0" - def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch): + def test_repr_traceback_with_invalid_cwd(self, importasmod, monkeypatch) -> None: mod = importasmod( """ def f(x): @@ -845,16 +806,47 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.entry) - p = FormattedExcinfo() + p = FormattedExcinfo(abspath=False) + + raised = 0 + + orig_getcwd = os.getcwd def raiseos(): - raise OSError(2) + nonlocal raised + upframe = sys._getframe().f_back + assert upframe is not None + if upframe.f_code.co_name == "checked_call": + # Only raise with expected calls, but not via e.g. inspect for + # py38-windows. + raised += 1 + raise OSError(2, "custom_oserror") + return orig_getcwd() monkeypatch.setattr(os, "getcwd", raiseos) assert p._makepath(__file__) == __file__ - p.repr_traceback(excinfo) + assert raised == 1 + repr_tb = p.repr_traceback(excinfo) + + matcher = LineMatcher(str(repr_tb).splitlines()) + matcher.fnmatch_lines( + [ + "def entry():", + "> f(0)", + "", + "{}:5: ".format(mod.__file__), + "_ _ *", + "", + " def f(x):", + "> raise ValueError(x)", + "E ValueError: 0", + "", + "{}:3: ValueError".format(mod.__file__), + ] + ) + assert raised == 3 - def test_repr_excinfo_addouterr(self, importasmod): + def test_repr_excinfo_addouterr(self, importasmod, tw_mock): mod = importasmod( """ def entry(): @@ -864,12 +856,11 @@ raise ValueError() excinfo = pytest.raises(ValueError, mod.entry) repr = excinfo.getrepr() repr.addsection("title", "content") - twmock = TWMock() - repr.toterminal(twmock) - assert twmock.lines[-1] == "content" - assert twmock.lines[-2] == ("-", "title") + repr.toterminal(tw_mock) + assert tw_mock.lines[-1] == "content" + assert tw_mock.lines[-2] == ("-", "title") - def test_repr_excinfo_reprcrash(self, importasmod): + def test_repr_excinfo_reprcrash(self, importasmod) -> None: mod = importasmod( """ def entry(): @@ -878,6 +869,7 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.entry) repr = excinfo.getrepr() + assert repr.reprcrash is not None assert repr.reprcrash.path.endswith("mod.py") assert repr.reprcrash.lineno == 3 assert repr.reprcrash.message == "ValueError" @@ -902,7 +894,7 @@ raise ValueError() assert reprtb.extraline == "!!! Recursion detected (same locals & position)" assert str(reprtb) - def test_reprexcinfo_getrepr(self, importasmod): + def test_reprexcinfo_getrepr(self, importasmod) -> None: mod = importasmod( """ def f(x): @@ -913,28 +905,27 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.entry) - for style in ("short", "long", "no"): + styles = ("short", "long", "no") # type: Tuple[_TracebackStyle, ...] + for style in styles: for showlocals in (True, False): repr = excinfo.getrepr(style=style, showlocals=showlocals) - if sys.version_info[0] < 3: - assert isinstance(repr, ReprExceptionInfo) assert repr.reprtraceback.style == style - if sys.version_info[0] >= 3: - assert isinstance(repr, ExceptionChainRepr) - for repr in repr.chain: - assert repr[0].style == style + + assert isinstance(repr, ExceptionChainRepr) + for r in repr.chain: + assert r[0].style == style def test_reprexcinfo_unicode(self): from _pytest._code.code import TerminalRepr class MyRepr(TerminalRepr): - def toterminal(self, tw): - tw.line(u"я") + def toterminal(self, tw: TerminalWriter) -> None: + tw.line("я") - x = six.text_type(MyRepr()) - assert x == u"я" + x = str(MyRepr()) + assert x == "я" - def test_toterminal_long(self, importasmod): + def test_toterminal_long(self, importasmod, tw_mock): mod = importasmod( """ def g(x): @@ -946,27 +937,26 @@ raise ValueError() excinfo = pytest.raises(ValueError, mod.f) excinfo.traceback = excinfo.traceback.filter() repr = excinfo.getrepr() - tw = TWMock() - repr.toterminal(tw) - assert tw.lines[0] == "" - tw.lines.pop(0) - assert tw.lines[0] == " def f():" - assert tw.lines[1] == "> g(3)" - assert tw.lines[2] == "" - line = tw.get_write_msg(3) + repr.toterminal(tw_mock) + assert tw_mock.lines[0] == "" + tw_mock.lines.pop(0) + assert tw_mock.lines[0] == " def f():" + assert tw_mock.lines[1] == "> g(3)" + assert tw_mock.lines[2] == "" + line = tw_mock.get_write_msg(3) assert line.endswith("mod.py") - assert tw.lines[4] == (":5: ") - assert tw.lines[5] == ("_ ", None) - assert tw.lines[6] == "" - assert tw.lines[7] == " def g(x):" - assert tw.lines[8] == "> raise ValueError(x)" - assert tw.lines[9] == "E ValueError: 3" - assert tw.lines[10] == "" - line = tw.get_write_msg(11) + assert tw_mock.lines[4] == (":5: ") + assert tw_mock.lines[5] == ("_ ", None) + assert tw_mock.lines[6] == "" + assert tw_mock.lines[7] == " def g(x):" + assert tw_mock.lines[8] == "> raise ValueError(x)" + assert tw_mock.lines[9] == "E ValueError: 3" + assert tw_mock.lines[10] == "" + line = tw_mock.get_write_msg(11) assert line.endswith("mod.py") - assert tw.lines[12] == ":3: ValueError" + assert tw_mock.lines[12] == ":3: ValueError" - def test_toterminal_long_missing_source(self, importasmod, tmpdir): + def test_toterminal_long_missing_source(self, importasmod, tmpdir, tw_mock): mod = importasmod( """ def g(x): @@ -979,25 +969,24 @@ raise ValueError() tmpdir.join("mod.py").remove() excinfo.traceback = excinfo.traceback.filter() repr = excinfo.getrepr() - tw = TWMock() - repr.toterminal(tw) - assert tw.lines[0] == "" - tw.lines.pop(0) - assert tw.lines[0] == "> ???" - assert tw.lines[1] == "" - line = tw.get_write_msg(2) + repr.toterminal(tw_mock) + assert tw_mock.lines[0] == "" + tw_mock.lines.pop(0) + assert tw_mock.lines[0] == "> ???" + assert tw_mock.lines[1] == "" + line = tw_mock.get_write_msg(2) assert line.endswith("mod.py") - assert tw.lines[3] == ":5: " - assert tw.lines[4] == ("_ ", None) - assert tw.lines[5] == "" - assert tw.lines[6] == "> ???" - assert tw.lines[7] == "E ValueError: 3" - assert tw.lines[8] == "" - line = tw.get_write_msg(9) + assert tw_mock.lines[3] == ":5: " + assert tw_mock.lines[4] == ("_ ", None) + assert tw_mock.lines[5] == "" + assert tw_mock.lines[6] == "> ???" + assert tw_mock.lines[7] == "E ValueError: 3" + assert tw_mock.lines[8] == "" + line = tw_mock.get_write_msg(9) assert line.endswith("mod.py") - assert tw.lines[10] == ":3: ValueError" + assert tw_mock.lines[10] == ":3: ValueError" - def test_toterminal_long_incomplete_source(self, importasmod, tmpdir): + def test_toterminal_long_incomplete_source(self, importasmod, tmpdir, tw_mock): mod = importasmod( """ def g(x): @@ -1010,25 +999,24 @@ raise ValueError() tmpdir.join("mod.py").write("asdf") excinfo.traceback = excinfo.traceback.filter() repr = excinfo.getrepr() - tw = TWMock() - repr.toterminal(tw) - assert tw.lines[0] == "" - tw.lines.pop(0) - assert tw.lines[0] == "> ???" - assert tw.lines[1] == "" - line = tw.get_write_msg(2) + repr.toterminal(tw_mock) + assert tw_mock.lines[0] == "" + tw_mock.lines.pop(0) + assert tw_mock.lines[0] == "> ???" + assert tw_mock.lines[1] == "" + line = tw_mock.get_write_msg(2) assert line.endswith("mod.py") - assert tw.lines[3] == ":5: " - assert tw.lines[4] == ("_ ", None) - assert tw.lines[5] == "" - assert tw.lines[6] == "> ???" - assert tw.lines[7] == "E ValueError: 3" - assert tw.lines[8] == "" - line = tw.get_write_msg(9) + assert tw_mock.lines[3] == ":5: " + assert tw_mock.lines[4] == ("_ ", None) + assert tw_mock.lines[5] == "" + assert tw_mock.lines[6] == "> ???" + assert tw_mock.lines[7] == "E ValueError: 3" + assert tw_mock.lines[8] == "" + line = tw_mock.get_write_msg(9) assert line.endswith("mod.py") - assert tw.lines[10] == ":3: ValueError" + assert tw_mock.lines[10] == ":3: ValueError" - def test_toterminal_long_filenames(self, importasmod): + def test_toterminal_long_filenames(self, importasmod, tw_mock): mod = importasmod( """ def f(): @@ -1036,23 +1024,22 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.f) - tw = TWMock() path = py.path.local(mod.__file__) old = path.dirpath().chdir() try: repr = excinfo.getrepr(abspath=False) - repr.toterminal(tw) + repr.toterminal(tw_mock) x = py.path.local().bestrelpath(path) if len(x) < len(str(path)): - msg = tw.get_write_msg(-2) + msg = tw_mock.get_write_msg(-2) assert msg == "mod.py" - assert tw.lines[-1] == ":3: ValueError" + assert tw_mock.lines[-1] == ":3: ValueError" repr = excinfo.getrepr(abspath=True) - repr.toterminal(tw) - msg = tw.get_write_msg(-2) + repr.toterminal(tw_mock) + msg = tw_mock.get_write_msg(-2) assert msg == path - line = tw.lines[-1] + line = tw_mock.lines[-1] assert line == ":3: ValueError" finally: old.chdir() @@ -1060,34 +1047,41 @@ raise ValueError() @pytest.mark.parametrize( "reproptions", [ - { - "style": style, - "showlocals": showlocals, - "funcargs": funcargs, - "tbfilter": tbfilter, - } - for style in ("long", "short", "no") + pytest.param( + { + "style": style, + "showlocals": showlocals, + "funcargs": funcargs, + "tbfilter": tbfilter, + }, + id="style={},showlocals={},funcargs={},tbfilter={}".format( + style, showlocals, funcargs, tbfilter + ), + ) + for style in ["long", "short", "line", "no", "native", "value", "auto"] for showlocals in (True, False) for tbfilter in (True, False) for funcargs in (True, False) ], ) - def test_format_excinfo(self, importasmod, reproptions): - mod = importasmod( - """ - def g(x): - raise ValueError(x) - def f(): - g(3) - """ - ) - excinfo = pytest.raises(ValueError, mod.f) - tw = py.io.TerminalWriter(stringio=True) + def test_format_excinfo(self, reproptions: Dict[str, Any]) -> None: + def bar(): + assert False, "some error" + + def foo(): + bar() + + # using inline functions as opposed to importasmod so we get source code lines + # in the tracebacks (otherwise getinspect doesn't find the source code). + with pytest.raises(AssertionError) as excinfo: + foo() + file = io.StringIO() + tw = TerminalWriter(file=file) repr = excinfo.getrepr(**reproptions) repr.toterminal(tw) - assert tw.stringio.getvalue() + assert file.getvalue() - def test_traceback_repr_style(self, importasmod): + def test_traceback_repr_style(self, importasmod, tw_mock): mod = importasmod( """ def f(): @@ -1105,36 +1099,34 @@ raise ValueError() excinfo.traceback[1].set_repr_style("short") excinfo.traceback[2].set_repr_style("short") r = excinfo.getrepr(style="long") - tw = TWMock() - r.toterminal(tw) - for line in tw.lines: + r.toterminal(tw_mock) + for line in tw_mock.lines: print(line) - assert tw.lines[0] == "" - assert tw.lines[1] == " def f():" - assert tw.lines[2] == "> g()" - assert tw.lines[3] == "" - msg = tw.get_write_msg(4) + assert tw_mock.lines[0] == "" + assert tw_mock.lines[1] == " def f():" + assert tw_mock.lines[2] == "> g()" + assert tw_mock.lines[3] == "" + msg = tw_mock.get_write_msg(4) assert msg.endswith("mod.py") - assert tw.lines[5] == ":3: " - assert tw.lines[6] == ("_ ", None) - tw.get_write_msg(7) - assert tw.lines[8].endswith("in g") - assert tw.lines[9] == " h()" - tw.get_write_msg(10) - assert tw.lines[11].endswith("in h") - assert tw.lines[12] == " i()" - assert tw.lines[13] == ("_ ", None) - assert tw.lines[14] == "" - assert tw.lines[15] == " def i():" - assert tw.lines[16] == "> raise ValueError()" - assert tw.lines[17] == "E ValueError" - assert tw.lines[18] == "" - msg = tw.get_write_msg(19) + assert tw_mock.lines[5] == ":3: " + assert tw_mock.lines[6] == ("_ ", None) + tw_mock.get_write_msg(7) + assert tw_mock.lines[8].endswith("in g") + assert tw_mock.lines[9] == " h()" + tw_mock.get_write_msg(10) + assert tw_mock.lines[11].endswith("in h") + assert tw_mock.lines[12] == " i()" + assert tw_mock.lines[13] == ("_ ", None) + assert tw_mock.lines[14] == "" + assert tw_mock.lines[15] == " def i():" + assert tw_mock.lines[16] == "> raise ValueError()" + assert tw_mock.lines[17] == "E ValueError" + assert tw_mock.lines[18] == "" + msg = tw_mock.get_write_msg(19) msg.endswith("mod.py") - assert tw.lines[20] == ":9: ValueError" + assert tw_mock.lines[20] == ":9: ValueError" - @pytest.mark.skipif("sys.version_info[0] < 3") - def test_exc_chain_repr(self, importasmod): + def test_exc_chain_repr(self, importasmod, tw_mock): mod = importasmod( """ class Err(Exception): @@ -1155,73 +1147,71 @@ raise ValueError() ) excinfo = pytest.raises(AttributeError, mod.f) r = excinfo.getrepr(style="long") - tw = TWMock() - r.toterminal(tw) - for line in tw.lines: + r.toterminal(tw_mock) + for line in tw_mock.lines: print(line) - assert tw.lines[0] == "" - assert tw.lines[1] == " def f():" - assert tw.lines[2] == " try:" - assert tw.lines[3] == "> g()" - assert tw.lines[4] == "" - line = tw.get_write_msg(5) + assert tw_mock.lines[0] == "" + assert tw_mock.lines[1] == " def f():" + assert tw_mock.lines[2] == " try:" + assert tw_mock.lines[3] == "> g()" + assert tw_mock.lines[4] == "" + line = tw_mock.get_write_msg(5) assert line.endswith("mod.py") - assert tw.lines[6] == ":6: " - assert tw.lines[7] == ("_ ", None) - assert tw.lines[8] == "" - assert tw.lines[9] == " def g():" - assert tw.lines[10] == "> raise ValueError()" - assert tw.lines[11] == "E ValueError" - assert tw.lines[12] == "" - line = tw.get_write_msg(13) + assert tw_mock.lines[6] == ":6: " + assert tw_mock.lines[7] == ("_ ", None) + assert tw_mock.lines[8] == "" + assert tw_mock.lines[9] == " def g():" + assert tw_mock.lines[10] == "> raise ValueError()" + assert tw_mock.lines[11] == "E ValueError" + assert tw_mock.lines[12] == "" + line = tw_mock.get_write_msg(13) assert line.endswith("mod.py") - assert tw.lines[14] == ":12: ValueError" - assert tw.lines[15] == "" + assert tw_mock.lines[14] == ":12: ValueError" + assert tw_mock.lines[15] == "" assert ( - tw.lines[16] + tw_mock.lines[16] == "The above exception was the direct cause of the following exception:" ) - assert tw.lines[17] == "" - assert tw.lines[18] == " def f():" - assert tw.lines[19] == " try:" - assert tw.lines[20] == " g()" - assert tw.lines[21] == " except Exception as e:" - assert tw.lines[22] == "> raise Err() from e" - assert tw.lines[23] == "E test_exc_chain_repr0.mod.Err" - assert tw.lines[24] == "" - line = tw.get_write_msg(25) + assert tw_mock.lines[17] == "" + assert tw_mock.lines[18] == " def f():" + assert tw_mock.lines[19] == " try:" + assert tw_mock.lines[20] == " g()" + assert tw_mock.lines[21] == " except Exception as e:" + assert tw_mock.lines[22] == "> raise Err() from e" + assert tw_mock.lines[23] == "E test_exc_chain_repr0.mod.Err" + assert tw_mock.lines[24] == "" + line = tw_mock.get_write_msg(25) assert line.endswith("mod.py") - assert tw.lines[26] == ":8: Err" - assert tw.lines[27] == "" + assert tw_mock.lines[26] == ":8: Err" + assert tw_mock.lines[27] == "" assert ( - tw.lines[28] + tw_mock.lines[28] == "During handling of the above exception, another exception occurred:" ) - assert tw.lines[29] == "" - assert tw.lines[30] == " def f():" - assert tw.lines[31] == " try:" - assert tw.lines[32] == " g()" - assert tw.lines[33] == " except Exception as e:" - assert tw.lines[34] == " raise Err() from e" - assert tw.lines[35] == " finally:" - assert tw.lines[36] == "> h()" - assert tw.lines[37] == "" - line = tw.get_write_msg(38) + assert tw_mock.lines[29] == "" + assert tw_mock.lines[30] == " def f():" + assert tw_mock.lines[31] == " try:" + assert tw_mock.lines[32] == " g()" + assert tw_mock.lines[33] == " except Exception as e:" + assert tw_mock.lines[34] == " raise Err() from e" + assert tw_mock.lines[35] == " finally:" + assert tw_mock.lines[36] == "> h()" + assert tw_mock.lines[37] == "" + line = tw_mock.get_write_msg(38) assert line.endswith("mod.py") - assert tw.lines[39] == ":10: " - assert tw.lines[40] == ("_ ", None) - assert tw.lines[41] == "" - assert tw.lines[42] == " def h():" - assert tw.lines[43] == "> raise AttributeError()" - assert tw.lines[44] == "E AttributeError" - assert tw.lines[45] == "" - line = tw.get_write_msg(46) + assert tw_mock.lines[39] == ":10: " + assert tw_mock.lines[40] == ("_ ", None) + assert tw_mock.lines[41] == "" + assert tw_mock.lines[42] == " def h():" + assert tw_mock.lines[43] == "> raise AttributeError()" + assert tw_mock.lines[44] == "E AttributeError" + assert tw_mock.lines[45] == "" + line = tw_mock.get_write_msg(46) assert line.endswith("mod.py") - assert tw.lines[47] == ":15: AttributeError" + assert tw_mock.lines[47] == ":15: AttributeError" - @pytest.mark.skipif("sys.version_info[0] < 3") @pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"]) - def test_exc_repr_chain_suppression(self, importasmod, mode): + def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock): """Check that exc repr does not show chained exceptions in Python 3. - When the exception is raised with "from None" - Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr(). @@ -1242,36 +1232,36 @@ raise ValueError() ) excinfo = pytest.raises(AttributeError, mod.f) r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress") - tw = TWMock() - r.toterminal(tw) - for line in tw.lines: + r.toterminal(tw_mock) + for line in tw_mock.lines: print(line) - assert tw.lines[0] == "" - assert tw.lines[1] == " def f():" - assert tw.lines[2] == " try:" - assert tw.lines[3] == " g()" - assert tw.lines[4] == " except Exception:" - assert tw.lines[5] == "> raise AttributeError(){}".format( + assert tw_mock.lines[0] == "" + assert tw_mock.lines[1] == " def f():" + assert tw_mock.lines[2] == " try:" + assert tw_mock.lines[3] == " g()" + assert tw_mock.lines[4] == " except Exception:" + assert tw_mock.lines[5] == "> raise AttributeError(){}".format( raise_suffix ) - assert tw.lines[6] == "E AttributeError" - assert tw.lines[7] == "" - line = tw.get_write_msg(8) + assert tw_mock.lines[6] == "E AttributeError" + assert tw_mock.lines[7] == "" + line = tw_mock.get_write_msg(8) assert line.endswith("mod.py") - assert tw.lines[9] == ":6: AttributeError" - assert len(tw.lines) == 10 + assert tw_mock.lines[9] == ":6: AttributeError" + assert len(tw_mock.lines) == 10 - @pytest.mark.skipif("sys.version_info[0] < 3") @pytest.mark.parametrize( "reason, description", [ - ( + pytest.param( "cause", "The above exception was the direct cause of the following exception:", + id="cause", ), - ( + pytest.param( "context", "During handling of the above exception, another exception occurred:", + id="context", ), ], ) @@ -1281,8 +1271,6 @@ raise ValueError() real traceback, such as those raised in a subprocess submitted by the multiprocessing module (#1984). """ - from _pytest.pytester import LineMatcher - exc_handling_code = " from e" if reason == "cause" else "" mod = importasmod( """ @@ -1306,11 +1294,12 @@ raise ValueError() getattr(excinfo.value, attr).__traceback__ = None r = excinfo.getrepr() - tw = py.io.TerminalWriter(stringio=True) + file = io.StringIO() + tw = TerminalWriter(file=file) tw.hasmarkup = False r.toterminal(tw) - matcher = LineMatcher(tw.stringio.getvalue().splitlines()) + matcher = LineMatcher(file.getvalue().splitlines()) matcher.fnmatch_lines( [ "ValueError: invalid value", @@ -1321,8 +1310,7 @@ raise ValueError() ] ) - @pytest.mark.skipif("sys.version_info[0] < 3") - def test_exc_chain_repr_cycle(self, importasmod): + def test_exc_chain_repr_cycle(self, importasmod, tw_mock): mod = importasmod( """ class Err(Exception): @@ -1343,9 +1331,8 @@ raise ValueError() ) excinfo = pytest.raises(ZeroDivisionError, mod.unreraise) r = excinfo.getrepr(style="short") - tw = TWMock() - r.toterminal(tw) - out = "\n".join(line for line in tw.lines if isinstance(line, str)) + r.toterminal(tw_mock) + out = "\n".join(line for line in tw_mock.lines if isinstance(line, str)) expected_out = textwrap.dedent( """\ :13: in unreraise @@ -1365,13 +1352,27 @@ raise ValueError() ) assert out == expected_out + def test_exec_type_error_filter(self, importasmod): + """See #7742""" + mod = importasmod( + """\ + def f(): + exec("a = 1", {}, []) + """ + ) + with pytest.raises(TypeError) as excinfo: + mod.f() + # previously crashed with `AttributeError: list has no attribute get` + excinfo.traceback.filter() + @pytest.mark.parametrize("style", ["short", "long"]) @pytest.mark.parametrize("encoding", [None, "utf8", "utf16"]) def test_repr_traceback_with_unicode(style, encoding): - msg = u"☹" - if encoding is not None: - msg = msg.encode(encoding) + if encoding is None: + msg = "☹" # type: Union[str, bytes] + else: + msg = "☹".encode(encoding) try: raise RuntimeError(msg) except RuntimeError: @@ -1392,7 +1393,8 @@ def test_cwd_deleted(testdir): ) result = testdir.runpytest() result.stdout.fnmatch_lines(["* 1 failed in *"]) - assert "INTERNALERROR" not in result.stdout.str() + result.stderr.str() + result.stdout.no_fnmatch_line("*INTERNALERROR*") + result.stderr.no_fnmatch_line("*INTERNALERROR*") @pytest.mark.usefixtures("limited_recursion_depth") @@ -1401,9 +1403,8 @@ def test_exception_repr_extraction_error_on_recursion(): Ensure we can properly detect a recursion error even if some locals raise error on comparison (#2459). """ - from _pytest.pytester import LineMatcher - class numpy_like(object): + class numpy_like: def __eq__(self, other): if type(other) is numpy_like: raise ValueError( @@ -1437,7 +1438,7 @@ def test_no_recursion_index_on_recursion_error(): during a recursion error (#2486). """ - class RecursionDepthError(object): + class RecursionDepthError: def __getattr__(self, attr): return getattr(self, "_" + attr) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py index d2e3c134cd5..d12c55d935b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/code/test_source.py @@ -1,25 +1,25 @@ -# -*- coding: utf-8 -*- # flake8: noqa # disable flake check on this file because some constructs are strange # or redundant on purpose and can't be disable on a line-by-line basis -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import ast import inspect +import linecache import sys +import textwrap +from types import CodeType +from typing import Any +from typing import Dict +from typing import Optional -import six +import py.path import _pytest._code import pytest +from _pytest._code import getfslineno from _pytest._code import Source -failsonjython = pytest.mark.xfail("sys.platform.startswith('java')") - -def test_source_str_function(): +def test_source_str_function() -> None: x = Source("3") assert str(x) == "3" @@ -34,21 +34,13 @@ def test_source_str_function(): assert str(x) == "\n3" -def test_unicode(): - x = Source(u"4") - assert str(x) == "4" - co = _pytest._code.compile(u'u"å"', mode="eval") - val = eval(co) - assert isinstance(val, six.text_type) - - -def test_source_from_function(): +def test_source_from_function() -> None: source = _pytest._code.Source(test_source_str_function) - assert str(source).startswith("def test_source_str_function():") + assert str(source).startswith("def test_source_str_function() -> None:") -def test_source_from_method(): - class TestClass(object): +def test_source_from_method() -> None: + class TestClass: def test_method(self): pass @@ -56,157 +48,78 @@ def test_source_from_method(): assert source.lines == ["def test_method(self):", " pass"] -def test_source_from_lines(): +def test_source_from_lines() -> None: lines = ["a \n", "b\n", "c"] source = _pytest._code.Source(lines) assert source.lines == ["a ", "b", "c"] -def test_source_from_inner_function(): +def test_source_from_inner_function() -> None: def f(): - pass + raise NotImplementedError() - source = _pytest._code.Source(f, deindent=False) - assert str(source).startswith(" def f():") source = _pytest._code.Source(f) assert str(source).startswith("def f():") -def test_source_putaround_simple(): - source = Source("raise ValueError") - source = source.putaround( - "try:", - """\ - except ValueError: - x = 42 - else: - x = 23""", - ) - assert ( - str(source) - == """\ -try: - raise ValueError -except ValueError: - x = 42 -else: - x = 23""" - ) - - -def test_source_putaround(): - source = Source() - source = source.putaround( - """ - if 1: - x=1 - """ - ) - assert str(source).strip() == "if 1:\n x=1" - - -def test_source_strips(): +def test_source_strips() -> None: source = Source("") assert source == Source() assert str(source) == "" assert source.strip() == source -def test_source_strip_multiline(): +def test_source_strip_multiline() -> None: source = Source() source.lines = ["", " hello", " "] source2 = source.strip() assert source2.lines == [" hello"] -def test_syntaxerror_rerepresentation(): - ex = pytest.raises(SyntaxError, _pytest._code.compile, "xyz xyz") - assert ex.value.lineno == 1 - assert ex.value.offset in (4, 5, 7) # XXX pypy/jython versus cpython? - assert ex.value.text.strip(), "x x" - - -def test_isparseable(): - assert Source("hello").isparseable() - assert Source("if 1:\n pass").isparseable() - assert Source(" \nif 1:\n pass").isparseable() - assert not Source("if 1:\n").isparseable() - assert not Source(" \nif 1:\npass").isparseable() - assert not Source(chr(0)).isparseable() - - -class TestAccesses(object): - source = Source( - """\ - def f(x): - pass - def g(x): - pass - """ - ) +class TestAccesses: + def setup_class(self) -> None: + self.source = Source( + """\ + def f(x): + pass + def g(x): + pass + """ + ) - def test_getrange(self): + def test_getrange(self) -> None: x = self.source[0:2] - assert x.isparseable() assert len(x.lines) == 2 assert str(x) == "def f(x):\n pass" - def test_getline(self): + def test_getrange_step_not_supported(self) -> None: + with pytest.raises(IndexError, match=r"step"): + self.source[::2] + + def test_getline(self) -> None: x = self.source[0] assert x == "def f(x):" - def test_len(self): + def test_len(self) -> None: assert len(self.source) == 4 - def test_iter(self): + def test_iter(self) -> None: values = [x for x in self.source] assert len(values) == 4 -class TestSourceParsingAndCompiling(object): - source = Source( - """\ - def f(x): - assert (x == - 3 + - 4) - """ - ).strip() - - def test_compile(self): - co = _pytest._code.compile("x=3") - d = {} - exec(co, d) - assert d["x"] == 3 - - def test_compile_and_getsource_simple(self): - co = _pytest._code.compile("x=3") - exec(co) - source = _pytest._code.Source(co) - assert str(source) == "x=3" - - def test_compile_and_getsource_through_same_function(self): - def gensource(source): - return _pytest._code.compile(source) - - co1 = gensource( - """ - def f(): - raise KeyError() - """ - ) - co2 = gensource( - """ - def f(): - raise ValueError() +class TestSourceParsing: + def setup_class(self) -> None: + self.source = Source( + """\ + def f(x): + assert (x == + 3 + + 4) """ - ) - source1 = inspect.getsource(co1) - assert "KeyError" in source1 - source2 = inspect.getsource(co2) - assert "ValueError" in source2 + ).strip() - def test_getstatement(self): + def test_getstatement(self) -> None: # print str(self.source) ass = str(self.source[1:]) for i in range(1, 4): @@ -215,18 +128,18 @@ class TestSourceParsingAndCompiling(object): # x = s.deindent() assert str(s) == ass - def test_getstatementrange_triple_quoted(self): + def test_getstatementrange_triple_quoted(self) -> None: # print str(self.source) source = Source( """hello(''' ''')""" ) s = source.getstatement(0) - assert s == str(source) + assert s == source s = source.getstatement(1) - assert s == str(source) + assert s == source - def test_getstatementrange_within_constructs(self): + def test_getstatementrange_within_constructs(self) -> None: source = Source( """\ try: @@ -248,7 +161,7 @@ class TestSourceParsingAndCompiling(object): # assert source.getstatementrange(5) == (0, 7) assert source.getstatementrange(6) == (6, 7) - def test_getstatementrange_bug(self): + def test_getstatementrange_bug(self) -> None: source = Source( """\ try: @@ -262,7 +175,7 @@ class TestSourceParsingAndCompiling(object): assert len(source) == 6 assert source.getstatementrange(2) == (1, 4) - def test_getstatementrange_bug2(self): + def test_getstatementrange_bug2(self) -> None: source = Source( """\ assert ( @@ -279,7 +192,7 @@ class TestSourceParsingAndCompiling(object): assert len(source) == 9 assert source.getstatementrange(5) == (0, 9) - def test_getstatementrange_ast_issue58(self): + def test_getstatementrange_ast_issue58(self) -> None: source = Source( """\ @@ -293,55 +206,19 @@ class TestSourceParsingAndCompiling(object): assert getstatement(2, source).lines == source.lines[2:3] assert getstatement(3, source).lines == source.lines[3:4] - def test_getstatementrange_out_of_bounds_py3(self): + def test_getstatementrange_out_of_bounds_py3(self) -> None: source = Source("if xxx:\n from .collections import something") r = source.getstatementrange(1) assert r == (1, 2) - def test_getstatementrange_with_syntaxerror_issue7(self): + def test_getstatementrange_with_syntaxerror_issue7(self) -> None: source = Source(":") pytest.raises(SyntaxError, lambda: source.getstatementrange(0)) - def test_compile_to_ast(self): - source = Source("x = 4") - mod = source.compile(flag=ast.PyCF_ONLY_AST) - assert isinstance(mod, ast.Module) - compile(mod, "<filename>", "exec") - - def test_compile_and_getsource(self): - co = self.source.compile() - exec(co, globals()) - f(7) - excinfo = pytest.raises(AssertionError, f, 6) - frame = excinfo.traceback[-1].frame - stmt = frame.code.fullsource.getstatement(frame.lineno) - assert str(stmt).strip().startswith("assert") - - @pytest.mark.parametrize("name", ["", None, "my"]) - def test_compilefuncs_and_path_sanity(self, name): - def check(comp, name): - co = comp(self.source, name) - if not name: - expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2) - else: - expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2) - fn = co.co_filename - assert fn.endswith(expected) - - mycode = _pytest._code.Code(self.test_compilefuncs_and_path_sanity) - mylineno = mycode.firstlineno - mypath = mycode.path - - for comp in _pytest._code.compile, _pytest._code.Source.compile: - check(comp, name) - - def test_offsetless_synerr(self): - pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode="eval") - - -def test_getstartingblock_singleline(): - class A(object): - def __init__(self, *args): + +def test_getstartingblock_singleline() -> None: + class A: + def __init__(self, *args) -> None: frame = sys._getframe(1) self.source = _pytest._code.Frame(frame).statement @@ -351,35 +228,35 @@ def test_getstartingblock_singleline(): assert len(values) == 1 -def test_getline_finally(): - def c(): +def test_getline_finally() -> None: + def c() -> None: pass with pytest.raises(TypeError) as excinfo: teardown = None try: - c(1) + c(1) # type: ignore finally: if teardown: - teardown() + teardown() # type: ignore[unreachable] source = excinfo.traceback[-1].statement - assert str(source).strip() == "c(1)" + assert str(source).strip() == "c(1) # type: ignore" -def test_getfuncsource_dynamic(): - source = """ - def f(): - raise ValueError +def test_getfuncsource_dynamic() -> None: + def f(): + raise NotImplementedError() - def g(): pass - """ - co = _pytest._code.compile(source) - exec(co, globals()) - assert str(_pytest._code.Source(f)).strip() == "def f():\n raise ValueError" - assert str(_pytest._code.Source(g)).strip() == "def g(): pass" + def g(): + pass # pragma: no cover + + f_source = _pytest._code.Source(f) + g_source = _pytest._code.Source(g) + assert str(f_source).strip() == "def f():\n raise NotImplementedError()" + assert str(g_source).strip() == "def g():\n pass # pragma: no cover" -def test_getfuncsource_with_multine_string(): +def test_getfuncsource_with_multine_string() -> None: def f(): c = """while True: pass @@ -394,7 +271,7 @@ def test_getfuncsource_with_multine_string(): assert str(_pytest._code.Source(f)) == expected.rstrip() -def test_deindent(): +def test_deindent() -> None: from _pytest._code.source import deindent as deindent assert deindent(["\tfoo", "\tbar"]) == ["foo", "bar"] @@ -408,7 +285,7 @@ def test_deindent(): assert lines == ["def f():", " def g():", " pass"] -def test_source_of_class_at_eof_without_newline(tmpdir, _sys_snapshot): +def test_source_of_class_at_eof_without_newline(tmpdir, _sys_snapshot) -> None: # this test fails because the implicit inspect.getsource(A) below # does not return the "x = 1" last line. source = _pytest._code.Source( @@ -430,115 +307,115 @@ if True: pass -def test_getsource_fallback(): - from _pytest._code.source import getsource - +def test_source_fallback() -> None: + src = Source(x) expected = """def x(): pass""" - src = getsource(x) - assert src == expected - - -def test_idem_compile_and_getsource(): - from _pytest._code.source import getsource + assert str(src) == expected - expected = "def x(): pass" - co = _pytest._code.compile(expected) - src = getsource(co) - assert src == expected - -def test_findsource_fallback(): +def test_findsource_fallback() -> None: from _pytest._code.source import findsource src, lineno = findsource(x) + assert src is not None assert "test_findsource_simple" in str(src) assert src[lineno] == " def x():" -def test_findsource(): +def test_findsource(monkeypatch) -> None: from _pytest._code.source import findsource - co = _pytest._code.compile( - """if 1: - def x(): - pass -""" - ) + filename = "<pytest-test_findsource>" + lines = ["if 1:\n", " def x():\n", " pass\n"] + co = compile("".join(lines), filename, "exec") + + # Type ignored because linecache.cache is private. + monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename)) # type: ignore[attr-defined] src, lineno = findsource(co) + assert src is not None assert "if 1:" in str(src) - d = {} + d = {} # type: Dict[str, Any] eval(co, d) src, lineno = findsource(d["x"]) + assert src is not None assert "if 1:" in str(src) assert src[lineno] == " def x():" -def test_getfslineno(): - from _pytest._code import getfslineno - - def f(x): - pass +def test_getfslineno() -> None: + def f(x) -> None: + raise NotImplementedError() fspath, lineno = getfslineno(f) + assert isinstance(fspath, py.path.local) assert fspath.basename == "test_source.py" - assert lineno == _pytest._code.getrawcode(f).co_firstlineno - 1 # see findsource + assert lineno == f.__code__.co_firstlineno - 1 # see findsource - class A(object): + class A: pass fspath, lineno = getfslineno(A) _, A_lineno = inspect.findsource(A) + assert isinstance(fspath, py.path.local) assert fspath.basename == "test_source.py" assert lineno == A_lineno assert getfslineno(3) == ("", -1) - class B(object): + class B: pass B.__name__ = B.__qualname__ = "B2" assert getfslineno(B)[1] == -1 + co = compile("...", "", "eval") + assert co.co_filename == "" -def test_code_of_object_instance_with_call(): - class A(object): + if hasattr(sys, "pypy_version_info"): + assert getfslineno(co) == ("", -1) + else: + assert getfslineno(co) == ("", 0) + + +def test_code_of_object_instance_with_call() -> None: + class A: pass pytest.raises(TypeError, lambda: _pytest._code.Source(A())) - class WithCall(object): - def __call__(self): + class WithCall: + def __call__(self) -> None: pass code = _pytest._code.Code(WithCall()) assert "pass" in str(code.source()) - class Hello(object): - def __call__(self): + class Hello: + def __call__(self) -> None: pass pytest.raises(TypeError, lambda: _pytest._code.Code(Hello)) -def getstatement(lineno, source): +def getstatement(lineno: int, source) -> Source: from _pytest._code.source import getstatementrange_ast - source = _pytest._code.Source(source, deindent=False) - ast, start, end = getstatementrange_ast(lineno, source) - return source[start:end] + src = _pytest._code.Source(source) + ast, start, end = getstatementrange_ast(lineno, src) + return src[start:end] -def test_oneline(): +def test_oneline() -> None: source = getstatement(0, "raise ValueError") assert str(source) == "raise ValueError" -def test_comment_and_no_newline_at_end(): +def test_comment_and_no_newline_at_end() -> None: from _pytest._code.source import getstatementrange_ast source = Source( @@ -552,12 +429,12 @@ def test_comment_and_no_newline_at_end(): assert end == 2 -def test_oneline_and_comment(): +def test_oneline_and_comment() -> None: source = getstatement(0, "raise ValueError\n#hello") assert str(source) == "raise ValueError" -def test_comments(): +def test_comments() -> None: source = '''def test(): "comment 1" x = 1 @@ -583,7 +460,7 @@ comment 4 assert str(getstatement(line, source)) == '"""\ncomment 4\n"""' -def test_comment_in_statement(): +def test_comment_in_statement() -> None: source = """test(foo=1, # comment 1 bar=2) @@ -595,17 +472,44 @@ def test_comment_in_statement(): ) -def test_single_line_else(): +def test_source_with_decorator() -> None: + """Test behavior with Source / Code().source with regard to decorators.""" + from _pytest.compat import get_real_func + + @pytest.mark.foo + def deco_mark(): + assert False + + src = inspect.getsource(deco_mark) + assert textwrap.indent(str(Source(deco_mark)), " ") + "\n" == src + assert src.startswith(" @pytest.mark.foo") + + @pytest.fixture + def deco_fixture(): + assert False + + src = inspect.getsource(deco_fixture) + assert src == " @pytest.fixture\n def deco_fixture():\n assert False\n" + # currenly Source does not unwrap decorators, testing the + # existing behavior here for explicitness, but perhaps we should revisit/change this + # in the future + assert str(Source(deco_fixture)).startswith("@functools.wraps(function)") + assert ( + textwrap.indent(str(Source(get_real_func(deco_fixture))), " ") + "\n" == src + ) + + +def test_single_line_else() -> None: source = getstatement(1, "if False: 2\nelse: 3") assert str(source) == "else: 3" -def test_single_line_finally(): +def test_single_line_finally() -> None: source = getstatement(1, "try: 1\nfinally: 3") assert str(source) == "finally: 3" -def test_issue55(): +def test_issue55() -> None: source = ( "def round_trip(dinp):\n assert 1 == dinp\n" 'def test_rt():\n round_trip("""\n""")\n' @@ -614,7 +518,7 @@ def test_issue55(): assert str(s) == ' round_trip("""\n""")' -def test_multiline(): +def test_multiline() -> None: source = getstatement( 0, """\ @@ -627,8 +531,9 @@ x = 3 assert str(source) == "raise ValueError(\n 23\n)" -class TestTry(object): - source = """\ +class TestTry: + def setup_class(self) -> None: + self.source = """\ try: raise ValueError except Something: @@ -637,42 +542,44 @@ else: raise KeyError() """ - def test_body(self): + def test_body(self) -> None: source = getstatement(1, self.source) assert str(source) == " raise ValueError" - def test_except_line(self): + def test_except_line(self) -> None: source = getstatement(2, self.source) assert str(source) == "except Something:" - def test_except_body(self): + def test_except_body(self) -> None: source = getstatement(3, self.source) assert str(source) == " raise IndexError(1)" - def test_else(self): + def test_else(self) -> None: source = getstatement(5, self.source) assert str(source) == " raise KeyError()" -class TestTryFinally(object): - source = """\ +class TestTryFinally: + def setup_class(self) -> None: + self.source = """\ try: raise ValueError finally: raise IndexError(1) """ - def test_body(self): + def test_body(self) -> None: source = getstatement(1, self.source) assert str(source) == " raise ValueError" - def test_finally(self): + def test_finally(self) -> None: source = getstatement(3, self.source) assert str(source) == " raise IndexError(1)" -class TestIf(object): - source = """\ +class TestIf: + def setup_class(self) -> None: + self.source = """\ if 1: y = 3 elif False: @@ -681,24 +588,24 @@ else: y = 7 """ - def test_body(self): + def test_body(self) -> None: source = getstatement(1, self.source) assert str(source) == " y = 3" - def test_elif_clause(self): + def test_elif_clause(self) -> None: source = getstatement(2, self.source) assert str(source) == "elif False:" - def test_elif(self): + def test_elif(self) -> None: source = getstatement(3, self.source) assert str(source) == " y = 5" - def test_else(self): + def test_else(self) -> None: source = getstatement(5, self.source) assert str(source) == " y = 7" -def test_semicolon(): +def test_semicolon() -> None: s = """\ hello ; pytest.skip() """ @@ -706,7 +613,7 @@ hello ; pytest.skip() assert str(source) == s.strip() -def test_def_online(): +def test_def_online() -> None: s = """\ def func(): raise ValueError(42) @@ -717,7 +624,7 @@ def something(): assert str(source) == "def func(): raise ValueError(42)" -def XXX_test_expression_multiline(): +def XXX_test_expression_multiline() -> None: source = """\ something ''' @@ -726,8 +633,8 @@ something assert str(result) == "'''\n'''" -def test_getstartingblock_multiline(): - class A(object): +def test_getstartingblock_multiline() -> None: + class A: def __init__(self, *args): frame = sys._getframe(1) self.source = _pytest._code.Frame(frame).statement diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/conftest.py index 627ee763d2f..a667be42fcb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/conftest.py @@ -1,7 +1,10 @@ -# -*- coding: utf-8 -*- +import re import sys +from typing import List import pytest +from _pytest.pytester import RunResult +from _pytest.pytester import Testdir if sys.gettrace(): @@ -18,7 +21,7 @@ if sys.gettrace(): @pytest.hookimpl(hookwrapper=True, tryfirst=True) -def pytest_collection_modifyitems(config, items): +def pytest_collection_modifyitems(items): """Prefer faster tests. Use a hookwrapper to do this in the beginning, so e.g. --ff still works @@ -40,9 +43,12 @@ def pytest_collection_modifyitems(config, items): neutral_items.append(item) else: if "testdir" in fixtures: - if spawn_names.intersection(item.function.__code__.co_names): + co_names = item.function.__code__.co_names + if spawn_names.intersection(co_names): item.add_marker(pytest.mark.uses_pexpect) slowest_items.append(item) + elif "runpytest_subprocess" in co_names: + slowest_items.append(item) else: slow_items.append(item) item.add_marker(pytest.mark.slow) @@ -56,3 +62,176 @@ def pytest_collection_modifyitems(config, items): items[:] = fast_items + neutral_items + slow_items + slowest_items yield + + +@pytest.fixture +def tw_mock(): + """Returns a mock terminal writer""" + + class TWMock: + WRITE = object() + + def __init__(self): + self.lines = [] + self.is_writing = False + + def sep(self, sep, line=None): + self.lines.append((sep, line)) + + def write(self, msg, **kw): + self.lines.append((TWMock.WRITE, msg)) + + def _write_source(self, lines, indents=()): + if not indents: + indents = [""] * len(lines) + for indent, line in zip(indents, lines): + self.line(indent + line) + + def line(self, line, **kw): + self.lines.append(line) + + def markup(self, text, **kw): + return text + + def get_write_msg(self, idx): + flag, msg = self.lines[idx] + assert flag == TWMock.WRITE + return msg + + fullwidth = 80 + + return TWMock() + + +@pytest.fixture +def dummy_yaml_custom_test(testdir): + """Writes a conftest file that collects and executes a dummy yaml test. + + Taken from the docs, but stripped down to the bare minimum, useful for + tests which needs custom items collected. + """ + testdir.makeconftest( + """ + import pytest + + def pytest_collect_file(parent, path): + if path.ext == ".yaml" and path.basename.startswith("test"): + return YamlFile.from_parent(fspath=path, parent=parent) + + class YamlFile(pytest.File): + def collect(self): + yield YamlItem.from_parent(name=self.fspath.basename, parent=self) + + class YamlItem(pytest.Item): + def runtest(self): + pass + """ + ) + testdir.makefile(".yaml", test1="") + + +@pytest.fixture +def testdir(testdir: Testdir) -> Testdir: + testdir.monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") + return testdir + + +@pytest.fixture(scope="session") +def color_mapping(): + """Returns a utility class which can replace keys in strings in the form "{NAME}" + by their equivalent ASCII codes in the terminal. + + Used by tests which check the actual colors output by pytest. + """ + + class ColorMapping: + COLORS = { + "red": "\x1b[31m", + "green": "\x1b[32m", + "yellow": "\x1b[33m", + "bold": "\x1b[1m", + "reset": "\x1b[0m", + "kw": "\x1b[94m", + "hl-reset": "\x1b[39;49;00m", + "function": "\x1b[92m", + "number": "\x1b[94m", + "str": "\x1b[33m", + "print": "\x1b[96m", + } + RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()} + + @classmethod + def format(cls, lines: List[str]) -> List[str]: + """Straightforward replacement of color names to their ASCII codes.""" + return [line.format(**cls.COLORS) for line in lines] + + @classmethod + def format_for_fnmatch(cls, lines: List[str]) -> List[str]: + """Replace color names for use with LineMatcher.fnmatch_lines""" + return [line.format(**cls.COLORS).replace("[", "[[]") for line in lines] + + @classmethod + def format_for_rematch(cls, lines: List[str]) -> List[str]: + """Replace color names for use with LineMatcher.re_match_lines""" + return [line.format(**cls.RE_COLORS) for line in lines] + + @classmethod + def requires_ordered_markup(cls, result: RunResult): + """Should be called if a test expects markup to appear in the output + in the order they were passed, for example: + + tw.write(line, bold=True, red=True) + + In Python 3.5 there's no guarantee that the generated markup will appear + in the order called, so we do some limited color testing and skip the rest of + the test. + """ + if sys.version_info < (3, 6): + # terminal writer.write accepts keyword arguments, so + # py36+ is required so the markup appears in the expected order + output = result.stdout.str() + assert "test session starts" in output + assert "\x1b[1m" in output + pytest.skip( + "doing limited testing because lacking ordered markup on py35" + ) + + return ColorMapping + + +@pytest.fixture +def mock_timing(monkeypatch): + """Mocks _pytest.timing with a known object that can be used to control timing in tests + deterministically. + + pytest itself should always use functions from `_pytest.timing` instead of `time` directly. + + This then allows us more control over time during testing, if testing code also + uses `_pytest.timing` functions. + + Time is static, and only advances through `sleep` calls, thus tests might sleep over large + numbers and obtain accurate time() calls at the end, making tests reliable and instant. + """ + import attr + + @attr.s + class MockTiming: + + _current_time = attr.ib(default=1590150050.0) + + def sleep(self, seconds): + self._current_time += seconds + + def time(self): + return self._current_time + + def patch(self): + from _pytest import timing + + monkeypatch.setattr(timing, "sleep", self.sleep) + monkeypatch.setattr(timing, "time", self.time) + monkeypatch.setattr(timing, "perf_counter", self.time) + + result = MockTiming() + result.patch() + return result diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py index 70460214fbd..eb5d527f52b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/deprecated_test.py @@ -1,231 +1,80 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os +import warnings +from unittest import mock import pytest -from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG - -pytestmark = pytest.mark.pytester_example_path("deprecated") - - -def test_pytest_setup_cfg_unsupported(testdir): - testdir.makefile( - ".cfg", - setup=""" - [pytest] - addopts = --verbose - """, - ) - with pytest.raises(pytest.fail.Exception): - testdir.runpytest() - - -def test_pytest_custom_cfg_unsupported(testdir): - testdir.makefile( - ".cfg", - custom=""" - [pytest] - addopts = --verbose - """, - ) - with pytest.raises(pytest.fail.Exception): - testdir.runpytest("-c", "custom.cfg") - - -def test_getfuncargvalue_is_deprecated(request): - pytest.deprecated_call(request.getfuncargvalue, "tmpdir") - - -@pytest.mark.filterwarnings("default") -def test_resultlog_is_deprecated(testdir): - result = testdir.runpytest("--help") - result.stdout.fnmatch_lines(["*DEPRECATED path for machine-readable result log*"]) +from _pytest import deprecated +from _pytest.pytester import Testdir - testdir.makepyfile( - """ - def test(): - pass - """ - ) - result = testdir.runpytest("--result-log=%s" % testdir.tmpdir.join("result.log")) - result.stdout.fnmatch_lines( - [ - "*--result-log is deprecated and scheduled for removal in pytest 5.0*", - "*See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information*", - ] - ) - - -def test_terminal_reporter_writer_attr(pytestconfig): - """Check that TerminalReporter._tw is also available as 'writer' (#2984) - This attribute is planned to be deprecated in 3.4. - """ - try: - import xdist # noqa - pytest.skip("xdist workers disable the terminal reporter plugin") - except ImportError: - pass - terminal_reporter = pytestconfig.pluginmanager.get_plugin("terminalreporter") - assert terminal_reporter.writer is terminal_reporter._tw +@pytest.mark.parametrize("attribute", pytest.collect.__all__) # type: ignore +# false positive due to dynamic attribute +def test_pytest_collect_module_deprecated(attribute): + with pytest.warns(DeprecationWarning, match=attribute): + getattr(pytest.collect, attribute) -@pytest.mark.parametrize("plugin", ["catchlog", "capturelog"]) +@pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS)) @pytest.mark.filterwarnings("default") -def test_pytest_catchlog_deprecated(testdir, plugin): - testdir.makepyfile( - """ - def test_func(pytestconfig): - pytestconfig.pluginmanager.register(None, 'pytest_{}') - """.format( - plugin - ) - ) - res = testdir.runpytest() - assert res.ret == 0 - res.stdout.fnmatch_lines( - ["*pytest-*log plugin has been merged into the core*", "*1 passed, 1 warnings*"] - ) +def test_external_plugins_integrated(testdir, plugin): + testdir.syspathinsert() + testdir.makepyfile(**{plugin: ""}) + with pytest.warns(pytest.PytestConfigWarning): + testdir.parseconfig("-p", plugin) -def test_raises_message_argument_deprecated(): - with pytest.warns(pytest.PytestDeprecationWarning): - with pytest.raises(RuntimeError, message="foobar"): - raise RuntimeError +def test_fillfuncargs_is_deprecated() -> None: + with pytest.warns( + pytest.PytestDeprecationWarning, + match="The `_fillfuncargs` function is deprecated", + ): + pytest._fillfuncargs(mock.Mock()) -def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir): - from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST - testdir.makepyfile( - **{ - "subdirectory/conftest.py": """ - pytest_plugins=['capture'] +def test_minus_k_dash_is_deprecated(testdir) -> None: + threepass = testdir.makepyfile( + test_threepass=""" + def test_one(): assert 1 + def test_two(): assert 1 + def test_three(): assert 1 """ - } - ) - testdir.makepyfile( - """ - def test_func(): - pass - """ - ) - res = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) - assert res.ret == 2 - msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] - res.stdout.fnmatch_lines( - ["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)] ) + result = testdir.runpytest("-k=-test_two", threepass) + result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"]) -@pytest.mark.parametrize("use_pyargs", [True, False]) -def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs( - testdir, use_pyargs -): - """When using --pyargs, do not emit the warning about non-top-level conftest warnings (#4039, #4044)""" - from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST - - files = { - "src/pkg/__init__.py": "", - "src/pkg/conftest.py": "", - "src/pkg/test_root.py": "def test(): pass", - "src/pkg/sub/__init__.py": "", - "src/pkg/sub/conftest.py": "pytest_plugins=['capture']", - "src/pkg/sub/test_bar.py": "def test(): pass", - } - testdir.makepyfile(**files) - testdir.syspathinsert(testdir.tmpdir.join("src")) - - args = ("--pyargs", "pkg") if use_pyargs else () - args += (SHOW_PYTEST_WARNINGS_ARG,) - res = testdir.runpytest(*args) - assert res.ret == (0 if use_pyargs else 2) - msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] - if use_pyargs: - assert msg not in res.stdout.str() - else: - res.stdout.fnmatch_lines(["*{msg}*".format(msg=msg)]) - - -def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conftest( - testdir, -): - from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST - - subdirectory = testdir.tmpdir.join("subdirectory") - subdirectory.mkdir() - testdir.makeconftest( - """ - pytest_plugins=['capture'] +def test_minus_k_colon_is_deprecated(testdir) -> None: + threepass = testdir.makepyfile( + test_threepass=""" + def test_one(): assert 1 + def test_two(): assert 1 + def test_three(): assert 1 """ ) - testdir.tmpdir.join("conftest.py").move(subdirectory.join("conftest.py")) + result = testdir.runpytest("-k", "test_two:", threepass) + result.stdout.fnmatch_lines(["*The `-k 'expr:'` syntax*deprecated*"]) - testdir.makepyfile( - """ - def test_func(): - pass - """ - ) - res = testdir.runpytest_subprocess() - assert res.ret == 2 - msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] - res.stdout.fnmatch_lines( - ["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)] - ) - - -def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives( - testdir, -): - from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST - - subdirectory = testdir.tmpdir.join("subdirectory") - subdirectory.mkdir() - testdir.makeconftest( +def test_fscollector_gethookproxy_isinitpath(testdir: Testdir) -> None: + module = testdir.getmodulecol( """ - pass - """ - ) - testdir.tmpdir.join("conftest.py").move(subdirectory.join("conftest.py")) - - testdir.makeconftest( - """ - import warnings - warnings.filterwarnings('always', category=DeprecationWarning) - pytest_plugins=['capture'] - """ - ) - testdir.makepyfile( - """ - def test_func(): - pass - """ - ) - res = testdir.runpytest_subprocess() - assert res.ret == 0 - msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] - assert msg not in res.stdout.str() - - -def test_fixture_named_request(testdir): - testdir.copy_example() - result = testdir.runpytest() - result.stdout.fnmatch_lines( - [ - "*'request' is a reserved name for fixtures and will raise an error in future versions" - ] - ) - - -def test_pytest_warns_unknown_kwargs(): - with pytest.warns( - PytestDeprecationWarning, - match=r"pytest.warns\(\) got unexpected keyword arguments: \['foo'\]", - ): - pytest.warns(UserWarning, foo="hello") + def test_foo(): pass + """, + withinit=True, + ) + assert isinstance(module, pytest.Module) + package = module.parent + assert isinstance(package, pytest.Package) + + with pytest.warns(pytest.PytestDeprecationWarning, match="gethookproxy"): + package.gethookproxy(testdir.tmpdir) + + with pytest.warns(pytest.PytestDeprecationWarning, match="isinitpath"): + package.isinitpath(testdir.tmpdir) + + # The methods on Session are *not* deprecated. + session = module.session + with warnings.catch_warnings(record=True) as rec: + session.gethookproxy(testdir.tmpdir) + session.isinitpath(testdir.tmpdir) + assert len(rec) == 0 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py index c8ccdf1b488..5b00ac90e1b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py @@ -1,10 +1,5 @@ -# -*- coding: utf-8 -*- """Reproduces issue #3774""" - -try: - import mock -except ImportError: - import unittest.mock as mock +from unittest import mock import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py index 6211055718f..9cd366295e7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_init(): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py index 8c560fd139f..8f2d73cfa4f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_foo(): pass diff --git a/tests/wpt/web-platform-tests/common/security-features/scope/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/__init__.pyi index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/common/security-features/scope/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py index 9c3d9a791c0..9629fa646af 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def pytest_ignore_collect(path): return False diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py index 10c5f605202..f174823854e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test(): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py index 10c5f605202..f174823854e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test(): pass diff --git a/tests/wpt/web-platform-tests/cookies/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/__init__.pyi index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/cookies/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/conftest.py index 13397ccc4c8..2da4ffe2fed 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/conftest.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- -class pytest_something(object): +class pytest_something: pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py index 8c560fd139f..8f2d73cfa4f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_foo(): pass diff --git a/tests/wpt/web-platform-tests/cookies/resources/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/__init__.pyi index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/cookies/resources/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py index d2f040578fa..8973e4252d3 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- def pytest_configure(config): import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses.py index 5381b5ed071..d96c90a91bd 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- from dataclasses import dataclass from dataclasses import field -def test_dataclasses(): +def test_dataclasses() -> None: @dataclass - class SimpleDataObject(object): + class SimpleDataObject: field_a: int = field() - field_b: int = field() + field_b: str = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py index d418dd6b0cf..7479c66c1be 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- from dataclasses import dataclass from dataclasses import field -def test_dataclasses_with_attribute_comparison_off(): +def test_dataclasses_with_attribute_comparison_off() -> None: @dataclass - class SimpleDataObject(object): + class SimpleDataObject: field_a: int = field() - field_b: int = field(compare=False) + field_b: str = field(compare=False) left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py index a72ca5119e6..4737ef904e0 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py @@ -1,13 +1,12 @@ -# -*- coding: utf-8 -*- from dataclasses import dataclass from dataclasses import field -def test_dataclasses_verbose(): +def test_dataclasses_verbose() -> None: @dataclass - class SimpleDataObject(object): + class SimpleDataObject: field_a: int = field() - field_b: int = field() + field_b: str = field() left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "c") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py new file mode 100644 index 00000000000..167140e16a6 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass + + +@dataclass +class S: + a: int + b: str + + +@dataclass +class C: + c: S + d: S + + +@dataclass +class C2: + e: C + f: S + + +@dataclass +class C3: + g: S + h: C2 + i: str + j: str + + +def test_recursive_dataclasses(): + left = C3( + S(10, "ten"), C2(C(S(1, "one"), S(2, "two")), S(2, "three")), "equal", "left", + ) + right = C3( + S(20, "xxx"), C2(C(S(1, "one"), S(2, "yyy")), S(3, "three")), "equal", "right", + ) + + assert left == right diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py index b77b70d3c2d..0a4820c69ba 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py @@ -1,20 +1,19 @@ -# -*- coding: utf-8 -*- from dataclasses import dataclass from dataclasses import field -def test_comparing_two_different_data_classes(): +def test_comparing_two_different_data_classes() -> None: @dataclass - class SimpleDataObjectOne(object): + class SimpleDataObjectOne: field_a: int = field() - field_b: int = field() + field_b: str = field() @dataclass - class SimpleDataObjectTwo(object): + class SimpleDataObjectTwo: field_a: int = field() - field_b: int = field() + field_b: str = field() left = SimpleDataObjectOne(1, "b") right = SimpleDataObjectTwo(1, "c") - assert left != right + assert left != right # type: ignore[comparison-overlap] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/deprecated/test_fixture_named_request.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/deprecated/test_fixture_named_request.py deleted file mode 100644 index 36addc29954..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/deprecated/test_fixture_named_request.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - - -@pytest.fixture -def request(): - pass - - -def test(): - pass diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/docroot/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/__init__.pyi index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/docroot/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py index fa41d0fba68..161934b58f7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py @@ -1,11 +1,15 @@ -# -*- coding: utf-8 -*- import pytest -class CustomItem(pytest.Item, pytest.File): +class CustomItem(pytest.Item): def runtest(self): pass +class CustomFile(pytest.File): + def collect(self): + yield CustomItem.from_parent(name="foo", parent=self) + + def pytest_collect_file(path, parent): - return CustomItem(path, parent) + return CustomFile.from_parent(fspath=path, parent=parent) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py index 10c5f605202..f174823854e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test(): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py index 952e7d60a69..be5adbeb6e5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- import pytest @pytest.fixture def arg1(request): - with pytest.raises(Exception): + with pytest.raises(pytest.FixtureLookupError): request.getfixturevalue("arg2") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py index b824f4e99ef..df36da1369b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_1(arg1): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py index 2be90b4dfe7..00981c5dc12 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py index a1432042bfd..1c34f94acc4 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_2(arg2): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py index d1e54eea7e4..d1efcbb338c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/docroot/subdir/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi index e69de29bb2d..e69de29bb2d 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/docroot/subdir/__init__.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py index 87756e6491f..5dfd2f77957 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py index 1cd88524b6f..4e22ce5a137 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py index 65690c49f23..0d891fbb503 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_spam(spam): assert spam == "spamspam" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py index 87756e6491f..5dfd2f77957 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py index 5b344c122f1..46d1446f470 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py index 4332c66017b..87a0c894111 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest @@ -7,7 +6,7 @@ def spam(): return "spam" -class TestSpam(object): +class TestSpam: @pytest.fixture def spam(self, spam): return spam * 2 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py index ba302c49e6c..0661cb301fc 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py index 1004d5d1352..256b92a17dd 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- import pytest -class TestClass(object): +class TestClass: @pytest.fixture def something(self, request): return request.instance diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py index 79d283e9f66..e15dbd2ca45 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest @@ -7,7 +6,7 @@ def something(request): return request.function.__name__ -class TestClass(object): +class TestClass: def test_method(self, something): assert something == "test_method" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py index 18f426af9c9..b775203231f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py new file mode 100644 index 00000000000..75514bf8b8c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py @@ -0,0 +1,10 @@ +import pytest + + +@pytest.fixture +def request(): + pass + + +def test(): + pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py index 219e57421d8..055a1220b1c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py index 8f3e7b4e52a..a053a638a9f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py @@ -1,14 +1,13 @@ -# -*- coding: utf-8 -*- import pytest class MyFile(pytest.File): def collect(self): - return [MyItem("hello", parent=self)] + return [MyItem.from_parent(name="hello", parent=self)] def pytest_collect_file(path, parent): - return MyFile(path, parent) + return MyFile.from_parent(fspath=path, parent=parent) class MyItem(pytest.Item): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py index 653d570a88c..56444d14748 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_hello(): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py index 24d864a5380..021dada4923 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/issue_519.py @@ -1,5 +1,6 @@ -# -*- coding: utf-8 -*- import pprint +from typing import List +from typing import Tuple import pytest @@ -14,7 +15,7 @@ def pytest_generate_tests(metafunc): @pytest.fixture(scope="session") def checked_order(): - order = [] + order = [] # type: List[Tuple[str, str, str]] yield order pprint.pprint(order) @@ -32,13 +33,13 @@ def checked_order(): ] -@pytest.yield_fixture(scope="module") +@pytest.fixture(scope="module") def fix1(request, arg1, checked_order): checked_order.append((request.node.name, "fix1", arg1)) yield "fix1-" + arg1 -@pytest.yield_fixture(scope="function") +@pytest.fixture(scope="function") def fix2(request, fix1, arg2, checked_order): checked_order.append((request.node.name, "fix2", arg2)) yield "fix2-" + arg2 + fix1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/junit-10.xsd b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/junit-10.xsd new file mode 100644 index 00000000000..286fbf7c87b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/junit-10.xsd @@ -0,0 +1,147 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- +The MIT License (MIT) + +Copyright (c) 2014, Gregory Boissinot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <xs:simpleType name="SUREFIRE_TIME"> + <xs:restriction base="xs:string"> + <xs:pattern value="(([0-9]{0,3},)*[0-9]{3}|[0-9]{0,3})*(\.[0-9]{0,3})?"/> + </xs:restriction> + </xs:simpleType> + + <xs:complexType name="rerunType" mixed="true"> <!-- mixed (XML contains text) to be compatible with version previous than 2.22.1 --> + <xs:sequence> + <xs:element name="stackTrace" type="xs:string" minOccurs="0" /> <!-- optional to be compatible with version previous than 2.22.1 --> + <xs:element name="system-out" type="xs:string" minOccurs="0" /> + <xs:element name="system-err" type="xs:string" minOccurs="0" /> + </xs:sequence> + <xs:attribute name="message" type="xs:string" /> + <xs:attribute name="type" type="xs:string" use="required" /> + </xs:complexType> + + <xs:element name="failure"> + <xs:complexType mixed="true"> + <xs:attribute name="type" type="xs:string"/> + <xs:attribute name="message" type="xs:string"/> + </xs:complexType> + </xs:element> + + <xs:element name="error"> + <xs:complexType mixed="true"> + <xs:attribute name="type" type="xs:string"/> + <xs:attribute name="message" type="xs:string"/> + </xs:complexType> + </xs:element> + + <xs:element name="skipped"> + <xs:complexType mixed="true"> + <xs:attribute name="type" type="xs:string"/> + <xs:attribute name="message" type="xs:string"/> + </xs:complexType> + </xs:element> + + <xs:element name="properties"> + <xs:complexType> + <xs:sequence> + <xs:element ref="property" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:element name="property"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="value" type="xs:string" use="required"/> + </xs:complexType> + </xs:element> + + <xs:element name="system-err" type="xs:string"/> + <xs:element name="system-out" type="xs:string"/> + <xs:element name="rerunFailure" type="rerunType"/> + <xs:element name="rerunError" type="rerunType"/> + <xs:element name="flakyFailure" type="rerunType"/> + <xs:element name="flakyError" type="rerunType"/> + + <xs:element name="testcase"> + <xs:complexType> + <xs:sequence> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element ref="skipped"/> + <xs:element ref="error"/> + <xs:element ref="failure"/> + <xs:element ref="rerunFailure" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="rerunError" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="flakyFailure" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="flakyError" minOccurs="0" maxOccurs="unbounded"/> + <xs:element ref="system-out"/> + <xs:element ref="system-err"/> + </xs:choice> + </xs:sequence> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="time" type="xs:string"/> + <xs:attribute name="classname" type="xs:string"/> + <xs:attribute name="group" type="xs:string"/> + </xs:complexType> + </xs:element> + + <xs:element name="testsuite"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element ref="testsuite"/> + <xs:element ref="properties"/> + <xs:element ref="testcase"/> + <xs:element ref="system-out"/> + <xs:element ref="system-err"/> + </xs:choice> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="tests" type="xs:string" use="required"/> + <xs:attribute name="failures" type="xs:string" use="required"/> + <xs:attribute name="errors" type="xs:string" use="required"/> + <xs:attribute name="group" type="xs:string" /> + <xs:attribute name="time" type="SUREFIRE_TIME"/> + <xs:attribute name="skipped" type="xs:string" /> + <xs:attribute name="timestamp" type="xs:string" /> + <xs:attribute name="hostname" type="xs:string" /> + <xs:attribute name="id" type="xs:string" /> + <xs:attribute name="package" type="xs:string" /> + <xs:attribute name="file" type="xs:string"/> + <xs:attribute name="log" type="xs:string"/> + <xs:attribute name="url" type="xs:string"/> + <xs:attribute name="version" type="xs:string"/> + </xs:complexType> + </xs:element> + + <xs:element name="testsuites"> + <xs:complexType> + <xs:sequence> + <xs:element ref="testsuite" minOccurs="0" maxOccurs="unbounded" /> + </xs:sequence> + <xs:attribute name="name" type="xs:string" /> + <xs:attribute name="time" type="SUREFIRE_TIME"/> + <xs:attribute name="tests" type="xs:string" /> + <xs:attribute name="failures" type="xs:string" /> + <xs:attribute name="errors" type="xs:string" /> + </xs:complexType> + </xs:element> + +</xs:schema> diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/__init__.pyi b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/__init__.pyi new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/__init__.pyi diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py index 61d969a36b8..35a2c7b7628 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py index 32ca818ead9..ff1eaf7d6bb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import argparse import pathlib diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py index 5d0cd5a7164..064ade190a1 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_x(): pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/pytest.ini b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/pytest.ini new file mode 100644 index 00000000000..ec5fe0e83a7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +# dummy pytest.ini to ease direct running of example scripts diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmpdir_fixture.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmpdir_fixture.py index 33f0315d52f..f4ad07462cb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmpdir_fixture.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmpdir_fixture.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py index cef950ee782..d421ce927c9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import unittest import pytest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py index 26b6195e96d..93f79bb3b2e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Skipping an entire subclass with unittest.skip() should *not* call setUp from a base class.""" import unittest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py index 0c4a3775723..4f251dcba17 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Skipping an entire subclass with unittest.skip() should *not* call setUpClass from a base class.""" import unittest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py index abf782a6217..98befbe510f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """setUpModule is always called, even if all tests in the module are skipped""" import unittest diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py new file mode 100644 index 00000000000..21b9d2cd963 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -0,0 +1,25 @@ +from typing import List +from unittest import IsolatedAsyncioTestCase # type: ignore + + +teardowns = [] # type: List[None] + + +class AsyncArguments(IsolatedAsyncioTestCase): + async def asyncTearDown(self): + teardowns.append(None) + + async def test_something_async(self): + async def addition(x, y): + return x + y + + self.assertEqual(await addition(2, 2), 4) + + async def test_something_async_fails(self): + async def addition(x, y): + return x + y + + self.assertEqual(await addition(2, 2), 3) + + def test_teardowns(self): + assert len(teardowns) == 2 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py new file mode 100644 index 00000000000..47b5f3f6d63 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py @@ -0,0 +1,23 @@ +"""Issue #7110""" +import asyncio +from typing import List + +import asynctest + + +teardowns = [] # type: List[None] + + +class Test(asynctest.TestCase): + async def tearDown(self): + teardowns.append(None) + + async def test_error(self): + await asyncio.sleep(0) + self.fail("failing on purpose") + + async def test_ok(self): + await asyncio.sleep(0) + + def test_teardowns(self): + assert len(teardowns) == 2 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py new file mode 100644 index 00000000000..78dfece684e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py @@ -0,0 +1,6 @@ +import unittest + + +class Test(unittest.TestCase): + async def test_foo(self): + assert False diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py index 29d89597452..6985caa4407 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py @@ -1,17 +1,21 @@ -# -*- coding: utf-8 -*- import warnings import pytest -def func(): - warnings.warn(UserWarning("foo")) +def func(msg): + warnings.warn(UserWarning(msg)) @pytest.mark.parametrize("i", range(5)) def test_foo(i): - func() + func("foo") -def test_bar(): - func() +def test_foo_1(): + func("foo") + + +@pytest.mark.parametrize("i", range(5)) +def test_bar(i): + func("bar") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py new file mode 100644 index 00000000000..b8c11cb71c9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py @@ -0,0 +1,21 @@ +import warnings + +import pytest + + +def func(msg): + warnings.warn(UserWarning(msg)) + + +@pytest.mark.parametrize("i", range(20)) +def test_foo(i): + func("foo") + + +def test_foo_1(): + func("foo") + + +@pytest.mark.parametrize("i", range(20)) +def test_bar(i): + func("bar") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py new file mode 100644 index 00000000000..636d04a5505 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py @@ -0,0 +1,5 @@ +from test_1 import func + + +def test_2(): + func("foo") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/examples/test_issue519.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/examples/test_issue519.py index 7da1f4ee664..e83f18fdc93 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/examples/test_issue519.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/examples/test_issue519.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- def test_510(testdir): testdir.copy_example("issue_519.py") testdir.runpytest("issue_519.py") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py index ab8013317b2..998df7b1ca7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/create_executable.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -""" -Generates an executable with pytest runner embedded using PyInstaller. -""" +"""Generate an executable with pytest runner embedded using PyInstaller.""" if __name__ == "__main__": import pytest import subprocess diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py index f7381f56863..591863016ac 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/runtests_script.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- """ This is the script that is actually frozen into an executable: simply executes -py.test main(). +pytest main(). """ if __name__ == "__main__": diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py index 449dbf9d362..08a55552abb 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- def test_upper(): assert "foo".upper() == "FOO" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py index 29b72792384..678a69c858a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/freeze/tox_run.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Called by tox.ini: uses the generated executable to run the tests in ./tests/ directory. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py index 90120308889..7a97cf424c5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_saferepr.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +import pytest +from _pytest._io.saferepr import _pformat_dispatch from _pytest._io.saferepr import saferepr @@ -16,7 +17,7 @@ def test_maxsize(): def test_maxsize_error_on_instance(): class A: - def __repr__(): + def __repr__(self): raise ValueError("...") s = saferepr(("*" * 50, A()), maxsize=25) @@ -24,7 +25,7 @@ def test_maxsize_error_on_instance(): assert s[0] == "(" and s[-1] == ")" -def test_exceptions(): +def test_exceptions() -> None: class BrokenRepr: def __init__(self, ex): self.ex = ex @@ -33,34 +34,140 @@ def test_exceptions(): raise self.ex class BrokenReprException(Exception): - __str__ = None - __repr__ = None + __str__ = None # type: ignore[assignment] + __repr__ = None # type: ignore[assignment] assert "Exception" in saferepr(BrokenRepr(Exception("broken"))) s = saferepr(BrokenReprException("really broken")) assert "TypeError" in s assert "TypeError" in saferepr(BrokenRepr("string")) - s2 = saferepr(BrokenRepr(BrokenReprException("omg even worse"))) - assert "NameError" not in s2 - assert "unknown" in s2 + none = None + try: + none() # type: ignore[misc] + except BaseException as exc: + exp_exc = repr(exc) + obj = BrokenRepr(BrokenReprException("omg even worse")) + s2 = saferepr(obj) + assert s2 == ( + "<[unpresentable exception ({!s}) raised in repr()] BrokenRepr object at 0x{:x}>".format( + exp_exc, id(obj) + ) + ) + + +def test_baseexception(): + """Test saferepr() with BaseExceptions, which includes pytest outcomes.""" + + class RaisingOnStrRepr(BaseException): + def __init__(self, exc_types): + self.exc_types = exc_types + + def raise_exc(self, *args): + try: + self.exc_type = self.exc_types.pop(0) + except IndexError: + pass + if hasattr(self.exc_type, "__call__"): + raise self.exc_type(*args) + raise self.exc_type + + def __str__(self): + self.raise_exc("__str__") + + def __repr__(self): + self.raise_exc("__repr__") + + class BrokenObj: + def __init__(self, exc): + self.exc = exc + + def __repr__(self): + raise self.exc + + __str__ = __repr__ + + baseexc_str = BaseException("__str__") + obj = BrokenObj(RaisingOnStrRepr([BaseException])) + assert saferepr(obj) == ( + "<[unpresentable exception ({!r}) " + "raised in repr()] BrokenObj object at 0x{:x}>".format(baseexc_str, id(obj)) + ) + obj = BrokenObj(RaisingOnStrRepr([RaisingOnStrRepr([BaseException])])) + assert saferepr(obj) == ( + "<[{!r} raised in repr()] BrokenObj object at 0x{:x}>".format( + baseexc_str, id(obj) + ) + ) + + with pytest.raises(KeyboardInterrupt): + saferepr(BrokenObj(KeyboardInterrupt())) + + with pytest.raises(SystemExit): + saferepr(BrokenObj(SystemExit())) + + with pytest.raises(KeyboardInterrupt): + saferepr(BrokenObj(RaisingOnStrRepr([KeyboardInterrupt]))) + + with pytest.raises(SystemExit): + saferepr(BrokenObj(RaisingOnStrRepr([SystemExit]))) + + with pytest.raises(KeyboardInterrupt): + print(saferepr(BrokenObj(RaisingOnStrRepr([BaseException, KeyboardInterrupt])))) + + with pytest.raises(SystemExit): + saferepr(BrokenObj(RaisingOnStrRepr([BaseException, SystemExit]))) + + +def test_buggy_builtin_repr(): + # Simulate a case where a repr for a builtin raises. + # reprlib dispatches by type name, so use "int". + + class int: + def __repr__(self): + raise ValueError("Buggy repr!") + + assert "Buggy" in saferepr(int()) def test_big_repr(): from _pytest._io.saferepr import SafeRepr - assert len(saferepr(range(1000))) <= len("[" + SafeRepr().maxlist * "1000" + "]") + assert len(saferepr(range(1000))) <= len("[" + SafeRepr(0).maxlist * "1000" + "]") -def test_repr_on_newstyle(): - class Function(object): +def test_repr_on_newstyle() -> None: + class Function: def __repr__(self): - return "<%s>" % (self.name) + return "<%s>" % (self.name) # type: ignore[attr-defined] assert saferepr(Function()) def test_unicode(): - val = u"£€" - reprval = u"'£€'" + val = "£€" + reprval = "'£€'" assert saferepr(val) == reprval + + +def test_pformat_dispatch(): + assert _pformat_dispatch("a") == "'a'" + assert _pformat_dispatch("a" * 10, width=5) == "'aaaaaaaaaa'" + assert _pformat_dispatch("foo bar", width=5) == "('foo '\n 'bar')" + + +def test_broken_getattribute(): + """saferepr() can create proper representations of classes with + broken __getattribute__ (#7145) + """ + + class SomeClass: + def __getattribute__(self, attr): + raise RuntimeError + + def __repr__(self): + raise RuntimeError + + assert saferepr(SomeClass()).startswith( + "<[RuntimeError() raised in repr()] SomeClass object at 0x" + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py new file mode 100644 index 00000000000..db0ccf06a40 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_terminalwriter.py @@ -0,0 +1,283 @@ +import io +import os +import re +import shutil +import sys +from typing import Generator +from unittest import mock + +import pytest +from _pytest._io import terminalwriter +from _pytest.monkeypatch import MonkeyPatch + + +# These tests were initially copied from py 1.8.1. + + +def test_terminal_width_COLUMNS(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setenv("COLUMNS", "42") + assert terminalwriter.get_terminal_width() == 42 + monkeypatch.delenv("COLUMNS", raising=False) + + +def test_terminalwriter_width_bogus(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setattr(shutil, "get_terminal_size", mock.Mock(return_value=(10, 10))) + monkeypatch.delenv("COLUMNS", raising=False) + tw = terminalwriter.TerminalWriter() + assert tw.fullwidth == 80 + + +def test_terminalwriter_computes_width(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setattr(terminalwriter, "get_terminal_width", lambda: 42) + tw = terminalwriter.TerminalWriter() + assert tw.fullwidth == 42 + + +def test_terminalwriter_dumb_term_no_markup(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setattr(os, "environ", {"TERM": "dumb", "PATH": ""}) + + class MyFile: + closed = False + + def isatty(self): + return True + + with monkeypatch.context() as m: + m.setattr(sys, "stdout", MyFile()) + assert sys.stdout.isatty() + tw = terminalwriter.TerminalWriter() + assert not tw.hasmarkup + + +def test_terminalwriter_not_unicode() -> None: + """If the file doesn't support Unicode, the string is unicode-escaped (#7475).""" + buffer = io.BytesIO() + file = io.TextIOWrapper(buffer, encoding="cp1252") + tw = terminalwriter.TerminalWriter(file) + tw.write("hello 🌀 wôrld אבג", flush=True) + assert buffer.getvalue() == br"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2" + + +win32 = int(sys.platform == "win32") + + +class TestTerminalWriter: + @pytest.fixture(params=["path", "stringio"]) + def tw( + self, request, tmpdir + ) -> Generator[terminalwriter.TerminalWriter, None, None]: + if request.param == "path": + p = tmpdir.join("tmpfile") + f = open(str(p), "w+", encoding="utf8") + tw = terminalwriter.TerminalWriter(f) + + def getlines(): + f.flush() + with open(str(p), encoding="utf8") as fp: + return fp.readlines() + + elif request.param == "stringio": + f = io.StringIO() + tw = terminalwriter.TerminalWriter(f) + + def getlines(): + f.seek(0) + return f.readlines() + + tw.getlines = getlines # type: ignore + tw.getvalue = lambda: "".join(getlines()) # type: ignore + + with f: + yield tw + + def test_line(self, tw) -> None: + tw.line("hello") + lines = tw.getlines() + assert len(lines) == 1 + assert lines[0] == "hello\n" + + def test_line_unicode(self, tw) -> None: + msg = "b\u00f6y" + tw.line(msg) + lines = tw.getlines() + assert lines[0] == msg + "\n" + + def test_sep_no_title(self, tw) -> None: + tw.sep("-", fullwidth=60) + lines = tw.getlines() + assert len(lines) == 1 + assert lines[0] == "-" * (60 - win32) + "\n" + + def test_sep_with_title(self, tw) -> None: + tw.sep("-", "hello", fullwidth=60) + lines = tw.getlines() + assert len(lines) == 1 + assert lines[0] == "-" * 26 + " hello " + "-" * (27 - win32) + "\n" + + def test_sep_longer_than_width(self, tw) -> None: + tw.sep("-", "a" * 10, fullwidth=5) + (line,) = tw.getlines() + # even though the string is wider than the line, still have a separator + assert line == "- aaaaaaaaaa -\n" + + @pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi") + @pytest.mark.parametrize("bold", (True, False)) + @pytest.mark.parametrize("color", ("red", "green")) + def test_markup(self, tw, bold: bool, color: str) -> None: + text = tw.markup("hello", **{color: True, "bold": bold}) + assert "hello" in text + + def test_markup_bad(self, tw) -> None: + with pytest.raises(ValueError): + tw.markup("x", wronkw=3) + with pytest.raises(ValueError): + tw.markup("x", wronkw=0) + + def test_line_write_markup(self, tw) -> None: + tw.hasmarkup = True + tw.line("x", bold=True) + tw.write("x\n", red=True) + lines = tw.getlines() + if sys.platform != "win32": + assert len(lines[0]) >= 2, lines + assert len(lines[1]) >= 2, lines + + def test_attr_fullwidth(self, tw) -> None: + tw.sep("-", "hello", fullwidth=70) + tw.fullwidth = 70 + tw.sep("-", "hello") + lines = tw.getlines() + assert len(lines[0]) == len(lines[1]) + + +@pytest.mark.skipif(sys.platform == "win32", reason="win32 has no native ansi") +def test_attr_hasmarkup() -> None: + file = io.StringIO() + tw = terminalwriter.TerminalWriter(file) + assert not tw.hasmarkup + tw.hasmarkup = True + tw.line("hello", bold=True) + s = file.getvalue() + assert len(s) > len("hello\n") + assert "\x1b[1m" in s + assert "\x1b[0m" in s + + +def assert_color_set(): + file = io.StringIO() + tw = terminalwriter.TerminalWriter(file) + assert tw.hasmarkup + tw.line("hello", bold=True) + s = file.getvalue() + assert len(s) > len("hello\n") + assert "\x1b[1m" in s + assert "\x1b[0m" in s + + +def assert_color_not_set(): + f = io.StringIO() + f.isatty = lambda: True # type: ignore + tw = terminalwriter.TerminalWriter(file=f) + assert not tw.hasmarkup + tw.line("hello", bold=True) + s = f.getvalue() + assert s == "hello\n" + + +def test_should_do_markup_PY_COLORS_eq_1(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setitem(os.environ, "PY_COLORS", "1") + assert_color_set() + + +def test_should_not_do_markup_PY_COLORS_eq_0(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setitem(os.environ, "PY_COLORS", "0") + assert_color_not_set() + + +def test_should_not_do_markup_NO_COLOR(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setitem(os.environ, "NO_COLOR", "1") + assert_color_not_set() + + +def test_should_do_markup_FORCE_COLOR(monkeypatch: MonkeyPatch) -> None: + monkeypatch.setitem(os.environ, "FORCE_COLOR", "1") + assert_color_set() + + +def test_should_not_do_markup_NO_COLOR_and_FORCE_COLOR( + monkeypatch: MonkeyPatch, +) -> None: + monkeypatch.setitem(os.environ, "NO_COLOR", "1") + monkeypatch.setitem(os.environ, "FORCE_COLOR", "1") + assert_color_not_set() + + +class TestTerminalWriterLineWidth: + def test_init(self) -> None: + tw = terminalwriter.TerminalWriter() + assert tw.width_of_current_line == 0 + + def test_update(self) -> None: + tw = terminalwriter.TerminalWriter() + tw.write("hello world") + assert tw.width_of_current_line == 11 + + def test_update_with_newline(self) -> None: + tw = terminalwriter.TerminalWriter() + tw.write("hello\nworld") + assert tw.width_of_current_line == 5 + + def test_update_with_wide_text(self) -> None: + tw = terminalwriter.TerminalWriter() + tw.write("乇乂ㄒ尺卂 ㄒ卄丨匚匚") + assert tw.width_of_current_line == 21 # 5*2 + 1 + 5*2 + + def test_composed(self) -> None: + tw = terminalwriter.TerminalWriter() + text = "café food" + assert len(text) == 9 + tw.write(text) + assert tw.width_of_current_line == 9 + + def test_combining(self) -> None: + tw = terminalwriter.TerminalWriter() + text = "café food" + assert len(text) == 10 + tw.write(text) + assert tw.width_of_current_line == 9 + + +@pytest.mark.parametrize( + ("has_markup", "code_highlight", "expected"), + [ + pytest.param( + True, + True, + "{kw}assert{hl-reset} {number}0{hl-reset}\n", + id="with markup and code_highlight", + ), + pytest.param( + True, False, "assert 0\n", id="with markup but no code_highlight", + ), + pytest.param( + False, True, "assert 0\n", id="without markup but with code_highlight", + ), + pytest.param( + False, False, "assert 0\n", id="neither markup nor code_highlight", + ), + ], +) +def test_code_highlight(has_markup, code_highlight, expected, color_mapping): + f = io.StringIO() + tw = terminalwriter.TerminalWriter(f) + tw.hasmarkup = has_markup + tw.code_highlight = code_highlight + tw._write_source(["assert 0"]) + + assert f.getvalue().splitlines(keepends=True) == color_mapping.format([expected]) + + with pytest.raises( + ValueError, + match=re.escape("indents size (2) should have same size as lines (1)"), + ): + tw._write_source(["assert 0"], [" ", " "]) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py new file mode 100644 index 00000000000..7cc74df5d07 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/io/test_wcwidth.py @@ -0,0 +1,38 @@ +import pytest +from _pytest._io.wcwidth import wcswidth +from _pytest._io.wcwidth import wcwidth + + +@pytest.mark.parametrize( + ("c", "expected"), + [ + ("\0", 0), + ("\n", -1), + ("a", 1), + ("1", 1), + ("א", 1), + ("\u200B", 0), + ("\u1ABE", 0), + ("\u0591", 0), + ("🉐", 2), + ("$", 2), + ], +) +def test_wcwidth(c: str, expected: int) -> None: + assert wcwidth(c) == expected + + +@pytest.mark.parametrize( + ("s", "expected"), + [ + ("", 0), + ("hello, world!", 13), + ("hello, world!\n", -1), + ("0123456789", 10), + ("שלום, עולם!", 11), + ("שְבֻעָיים", 6), + ("🉐🉐🉐", 6), + ], +) +def test_wcswidth(s: str, expected: int) -> None: + assert wcswidth(s) == expected diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py index ff772e7ec62..ffd51bcad7a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_fixture.py @@ -1,7 +1,8 @@ -# -*- coding: utf-8 -*- import logging import pytest +from _pytest.logging import caplog_records_key +from _pytest.pytester import Testdir logger = logging.getLogger(__name__) sublogger = logging.getLogger(__name__ + ".baz") @@ -27,8 +28,11 @@ def test_change_level(caplog): assert "CRITICAL" in caplog.text -def test_change_level_undo(testdir): - """Ensure that 'set_level' is undone after the end of the test""" +def test_change_level_undo(testdir: Testdir) -> None: + """Ensure that 'set_level' is undone after the end of the test. + + Tests the logging output themselves (affacted both by logger and handler levels). + """ testdir.makepyfile( """ import logging @@ -47,7 +51,35 @@ def test_change_level_undo(testdir): ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"]) - assert "log from test2" not in result.stdout.str() + result.stdout.no_fnmatch_line("*log from test2*") + + +def test_change_level_undos_handler_level(testdir: Testdir) -> None: + """Ensure that 'set_level' is undone after the end of the test (handler). + + Issue #7569. Tests the handler level specifically. + """ + testdir.makepyfile( + """ + import logging + + def test1(caplog): + assert caplog.handler.level == 0 + caplog.set_level(9999) + caplog.set_level(41) + assert caplog.handler.level == 41 + + def test2(caplog): + assert caplog.handler.level == 0 + + def test3(caplog): + assert caplog.handler.level == 0 + caplog.set_level(43) + assert caplog.handler.level == 43 + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=3) def test_with_statement(caplog): @@ -103,15 +135,15 @@ def test_record_tuples(caplog): def test_unicode(caplog): caplog.set_level(logging.INFO) - logger.info(u"bū") + logger.info("bū") assert caplog.records[0].levelname == "INFO" - assert caplog.records[0].msg == u"bū" - assert u"bū" in caplog.text + assert caplog.records[0].msg == "bū" + assert "bū" in caplog.text def test_clear(caplog): caplog.set_level(logging.INFO) - logger.info(u"bū") + logger.info("bū") assert len(caplog.records) assert caplog.text caplog.clear() @@ -137,4 +169,140 @@ def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardow assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] # This reaches into private API, don't use this type of thing in real tests! - assert set(caplog._item.catch_log_handlers.keys()) == {"setup", "call"} + assert set(caplog._item._store[caplog_records_key]) == {"setup", "call"} + + +def test_ini_controls_global_log_level(testdir): + testdir.makepyfile( + """ + import pytest + import logging + def test_log_level_override(request, caplog): + plugin = request.config.pluginmanager.getplugin('logging-plugin') + assert plugin.log_level == logging.ERROR + logger = logging.getLogger('catchlog') + logger.warning("WARNING message won't be shown") + logger.error("ERROR message will be shown") + assert 'WARNING' not in caplog.text + assert 'ERROR' in caplog.text + """ + ) + testdir.makeini( + """ + [pytest] + log_level=ERROR + """ + ) + + result = testdir.runpytest() + # make sure that that we get a '0' exit code for the testsuite + assert result.ret == 0 + + +def test_caplog_can_override_global_log_level(testdir): + testdir.makepyfile( + """ + import pytest + import logging + def test_log_level_override(request, caplog): + logger = logging.getLogger('catchlog') + plugin = request.config.pluginmanager.getplugin('logging-plugin') + assert plugin.log_level == logging.WARNING + + logger.info("INFO message won't be shown") + + caplog.set_level(logging.INFO, logger.name) + + with caplog.at_level(logging.DEBUG, logger.name): + logger.debug("DEBUG message will be shown") + + logger.debug("DEBUG message won't be shown") + + with caplog.at_level(logging.CRITICAL, logger.name): + logger.warning("WARNING message won't be shown") + + logger.debug("DEBUG message won't be shown") + logger.info("INFO message will be shown") + + assert "message won't be shown" not in caplog.text + """ + ) + testdir.makeini( + """ + [pytest] + log_level=WARNING + """ + ) + + result = testdir.runpytest() + assert result.ret == 0 + + +def test_caplog_captures_despite_exception(testdir): + testdir.makepyfile( + """ + import pytest + import logging + def test_log_level_override(request, caplog): + logger = logging.getLogger('catchlog') + plugin = request.config.pluginmanager.getplugin('logging-plugin') + assert plugin.log_level == logging.WARNING + + logger.error("ERROR message " + "will be shown") + + with caplog.at_level(logging.DEBUG, logger.name): + logger.debug("DEBUG message " + "won't be shown") + raise Exception() + """ + ) + testdir.makeini( + """ + [pytest] + log_level=WARNING + """ + ) + + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*ERROR message will be shown*"]) + result.stdout.no_fnmatch_line("*DEBUG message won't be shown*") + assert result.ret == 1 + + +def test_log_report_captures_according_to_config_option_upon_failure(testdir): + """Test that upon failure: + (1) `caplog` succeeded to capture the DEBUG message and assert on it => No `Exception` is raised. + (2) The `DEBUG` message does NOT appear in the `Captured log call` report. + (3) The stdout, `INFO`, and `WARNING` messages DO appear in the test reports due to `--log-level=INFO`. + """ + testdir.makepyfile( + """ + import pytest + import logging + + def function_that_logs(): + logging.debug('DEBUG log ' + 'message') + logging.info('INFO log ' + 'message') + logging.warning('WARNING log ' + 'message') + print('Print ' + 'message') + + def test_that_fails(request, caplog): + plugin = request.config.pluginmanager.getplugin('logging-plugin') + assert plugin.log_level == logging.INFO + + with caplog.at_level(logging.DEBUG): + function_that_logs() + + if 'DEBUG log ' + 'message' not in caplog.text: + raise Exception('caplog failed to ' + 'capture DEBUG') + + assert False + """ + ) + + result = testdir.runpytest("--log-level=INFO") + result.stdout.no_fnmatch_line("*Exception: caplog failed to capture DEBUG*") + result.stdout.no_fnmatch_line("*DEBUG log message*") + result.stdout.fnmatch_lines( + ["*Print message*", "*INFO log message*", "*WARNING log message*"] + ) + assert result.ret == 1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_formatter.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_formatter.py index c851c34d784..a90384a9553 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_formatter.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_formatter.py @@ -1,14 +1,11 @@ -# -*- coding: utf-8 -*- import logging +from typing import Any -import py.io -import six - -import pytest +from _pytest._io import TerminalWriter from _pytest.logging import ColoredLevelFormatter -def test_coloredlogformatter(): +def test_coloredlogformatter() -> None: logfmt = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" record = logging.LogRecord( @@ -18,14 +15,14 @@ def test_coloredlogformatter(): lineno=10, msg="Test Message", args=(), - exc_info=False, + exc_info=None, ) - class ColorConfig(object): - class option(object): + class ColorConfig: + class option: pass - tw = py.io.TerminalWriter() + tw = TerminalWriter() tw.hasmarkup = True formatter = ColoredLevelFormatter(tw, logfmt) output = formatter.format(record) @@ -39,10 +36,7 @@ def test_coloredlogformatter(): assert output == ("dummypath 10 INFO Test Message") -@pytest.mark.skipif( - six.PY2, reason="Formatter classes don't support format styles in PY2" -) -def test_multiline_message(): +def test_multiline_message() -> None: from _pytest.logging import PercentStyleMultiline logfmt = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" @@ -54,14 +48,103 @@ def test_multiline_message(): lineno=10, msg="Test Message line1\nline2", args=(), - exc_info=False, - ) + exc_info=None, + ) # type: Any # this is called by logging.Formatter.format record.message = record.getMessage() - style = PercentStyleMultiline(logfmt) - output = style.format(record) + ai_on_style = PercentStyleMultiline(logfmt, True) + output = ai_on_style.format(record) assert output == ( "dummypath 10 INFO Test Message line1\n" " line2" ) + + ai_off_style = PercentStyleMultiline(logfmt, False) + output = ai_off_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\nline2" + ) + + ai_none_style = PercentStyleMultiline(logfmt, None) + output = ai_none_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\nline2" + ) + + record.auto_indent = False + output = ai_on_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\nline2" + ) + + record.auto_indent = True + output = ai_off_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\n" + " line2" + ) + + record.auto_indent = "False" + output = ai_on_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\nline2" + ) + + record.auto_indent = "True" + output = ai_off_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\n" + " line2" + ) + + # bad string values default to False + record.auto_indent = "junk" + output = ai_off_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\nline2" + ) + + # anything other than string or int will default to False + record.auto_indent = dict() + output = ai_off_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\nline2" + ) + + record.auto_indent = "5" + output = ai_off_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\n line2" + ) + + record.auto_indent = 5 + output = ai_off_style.format(record) + assert output == ( + "dummypath 10 INFO Test Message line1\n line2" + ) + + +def test_colored_short_level() -> None: + logfmt = "%(levelname).1s %(message)s" + + record = logging.LogRecord( + name="dummy", + level=logging.INFO, + pathname="dummypath", + lineno=10, + msg="Test Message", + args=(), + exc_info=None, + ) + + class ColorConfig: + class option: + pass + + tw = TerminalWriter() + tw.hasmarkup = True + formatter = ColoredLevelFormatter(tw, logfmt) + output = formatter.format(record) + # the I (of INFO) is colored + assert output == ("\x1b[32mI\x1b[0m Test Message") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py index 6ff5ccfb55a..7590b576289 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/logging/test_reporting.py @@ -1,11 +1,13 @@ -# -*- coding: utf-8 -*- +import io import os import re -from io import open - -import six +from typing import cast import pytest +from _pytest.capture import CaptureManager +from _pytest.config import ExitCode +from _pytest.pytester import Testdir +from _pytest.terminal import TerminalReporter def test_nothing_logged(testdir): @@ -112,7 +114,7 @@ def test_log_cli_level_log_level_interaction(testdir): "=* 1 failed in *=", ] ) - assert "DEBUG" not in result.stdout.str() + result.stdout.no_re_match_line("DEBUG") def test_setup_logging(testdir): @@ -169,60 +171,6 @@ def test_teardown_logging(testdir): ) -def test_disable_log_capturing(testdir): - testdir.makepyfile( - """ - import sys - import logging - - logger = logging.getLogger(__name__) - - def test_foo(): - sys.stdout.write('text going to stdout') - logger.warning('catch me if you can!') - sys.stderr.write('text going to stderr') - assert False - """ - ) - result = testdir.runpytest("--no-print-logs") - print(result.stdout) - assert result.ret == 1 - result.stdout.fnmatch_lines(["*- Captured stdout call -*", "text going to stdout"]) - result.stdout.fnmatch_lines(["*- Captured stderr call -*", "text going to stderr"]) - with pytest.raises(pytest.fail.Exception): - result.stdout.fnmatch_lines(["*- Captured *log call -*"]) - - -def test_disable_log_capturing_ini(testdir): - testdir.makeini( - """ - [pytest] - log_print=False - """ - ) - testdir.makepyfile( - """ - import sys - import logging - - logger = logging.getLogger(__name__) - - def test_foo(): - sys.stdout.write('text going to stdout') - logger.warning('catch me if you can!') - sys.stderr.write('text going to stderr') - assert False - """ - ) - result = testdir.runpytest() - print(result.stdout) - assert result.ret == 1 - result.stdout.fnmatch_lines(["*- Captured stdout call -*", "text going to stdout"]) - result.stdout.fnmatch_lines(["*- Captured stderr call -*", "text going to stderr"]) - with pytest.raises(pytest.fail.Exception): - result.stdout.fnmatch_lines(["*- Captured *log call -*"]) - - @pytest.mark.parametrize("enabled", [True, False]) def test_log_cli_enabled_disabled(testdir, enabled): msg = "critical message logged by test" @@ -285,7 +233,7 @@ def test_log_cli_default_level(testdir): "WARNING*test_log_cli_default_level.py* message will be shown*", ] ) - assert "INFO message won't be shown" not in result.stdout.str() + result.stdout.no_fnmatch_line("*INFO message won't be shown*") # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 @@ -569,7 +517,7 @@ def test_log_cli_level(testdir): "PASSED", # 'PASSED' on its own line because the log message prints a new line ] ) - assert "This log message won't be shown" not in result.stdout.str() + result.stdout.no_fnmatch_line("*This log message won't be shown*") # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 @@ -583,7 +531,7 @@ def test_log_cli_level(testdir): "PASSED", # 'PASSED' on its own line because the log message prints a new line ] ) - assert "This log message won't be shown" not in result.stdout.str() + result.stdout.no_fnmatch_line("*This log message won't be shown*") # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 @@ -619,7 +567,7 @@ def test_log_cli_ini_level(testdir): "PASSED", # 'PASSED' on its own line because the log message prints a new line ] ) - assert "This log message won't be shown" not in result.stdout.str() + result.stdout.no_fnmatch_line("*This log message won't be shown*") # make sure that that we get a '0' exit code for the testsuite assert result.ret == 0 @@ -629,7 +577,7 @@ def test_log_cli_ini_level(testdir): "cli_args", ["", "--log-level=WARNING", "--log-file-level=WARNING", "--log-cli-level=WARNING"], ) -def test_log_cli_auto_enable(testdir, request, cli_args): +def test_log_cli_auto_enable(testdir, cli_args): """Check that live logs are enabled if --log-level or --log-cli-level is passed on the CLI. It should not be auto enabled if the same configs are set on the INI file. """ @@ -841,16 +789,14 @@ def test_log_file_unicode(testdir): ) ) testdir.makepyfile( - """ - # -*- coding: utf-8 -*- - from __future__ import unicode_literals + """\ import logging def test_log_file(): logging.getLogger('catchlog').info("Normal message") logging.getLogger('catchlog').info("├") logging.getLogger('catchlog').info("Another normal message") - """ + """ ) result = testdir.runpytest() @@ -861,12 +807,12 @@ def test_log_file_unicode(testdir): with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "Normal message" in contents - assert u"├" in contents + assert "├" in contents assert "Another normal message" in contents @pytest.mark.parametrize("has_capture_manager", [True, False]) -def test_live_logging_suspends_capture(has_capture_manager, request): +def test_live_logging_suspends_capture(has_capture_manager: bool, request) -> None: """Test that capture manager is suspended when we emitting messages for live logging. This tests the implementation calls instead of behavior because it is difficult/impossible to do it using @@ -889,12 +835,14 @@ def test_live_logging_suspends_capture(has_capture_manager, request): yield self.calls.append("exit disabled") - class DummyTerminal(six.StringIO): + class DummyTerminal(io.StringIO): def section(self, *args, **kwargs): pass - out_file = DummyTerminal() - capture_manager = MockCaptureManager() if has_capture_manager else None + out_file = cast(TerminalReporter, DummyTerminal()) + capture_manager = ( + cast(CaptureManager, MockCaptureManager()) if has_capture_manager else None + ) handler = _LiveLoggingStreamHandler(out_file, capture_manager) handler.set_when("call") @@ -907,7 +855,7 @@ def test_live_logging_suspends_capture(has_capture_manager, request): assert MockCaptureManager.calls == ["enter disabled", "exit disabled"] else: assert MockCaptureManager.calls == [] - assert out_file.getvalue() == "\nsome message\n" + assert cast(io.StringIO, out_file).getvalue() == "\nsome message\n" def test_collection_live_logging(testdir): @@ -947,15 +895,15 @@ def test_collection_collect_only_live_logging(testdir, verbose): ] ) elif verbose == "-q": - assert "collected 1 item*" not in result.stdout.str() + result.stdout.no_fnmatch_line("*collected 1 item**") expected_lines.extend( [ "*test_collection_collect_only_live_logging.py::test_simple*", - "no tests ran in * seconds", + "no tests ran in [0-9].[0-9][0-9]s", ] ) elif verbose == "-qq": - assert "collected 1 item*" not in result.stdout.str() + result.stdout.no_fnmatch_line("*collected 1 item**") expected_lines.extend(["*test_collection_collect_only_live_logging.py: 1*"]) result.stdout.fnmatch_lines(expected_lines) @@ -988,7 +936,7 @@ def test_collection_logging_to_file(testdir): result = testdir.runpytest() - assert "--- live log collection ---" not in result.stdout.str() + result.stdout.no_fnmatch_line("*--- live log collection ---*") assert result.ret == 0 assert os.path.isfile(log_file) @@ -1108,20 +1056,18 @@ def test_log_set_path(testdir): """ ) testdir.runpytest() - with open(os.path.join(report_dir_base, "test_first"), "r") as rfh: + with open(os.path.join(report_dir_base, "test_first")) as rfh: content = rfh.read() assert "message from test 1" in content - with open(os.path.join(report_dir_base, "test_second"), "r") as rfh: + with open(os.path.join(report_dir_base, "test_second")) as rfh: content = rfh.read() assert "message from test 2" in content def test_colored_captured_log(testdir): - """ - Test that the level names of captured log messages of a failing test are - colored. - """ + """Test that the level names of captured log messages of a failing test + are colored.""" testdir.makepyfile( """ import logging @@ -1144,9 +1090,7 @@ def test_colored_captured_log(testdir): def test_colored_ansi_esc_caplogtext(testdir): - """ - Make sure that caplog.text does not contain ANSI escape sequences. - """ + """Make sure that caplog.text does not contain ANSI escape sequences.""" testdir.makepyfile( """ import logging @@ -1160,3 +1104,53 @@ def test_colored_ansi_esc_caplogtext(testdir): ) result = testdir.runpytest("--log-level=INFO", "--color=yes") assert result.ret == 0 + + +def test_logging_emit_error(testdir: Testdir) -> None: + """An exception raised during emit() should fail the test. + + The default behavior of logging is to print "Logging error" + to stderr with the call stack and some extra details. + + pytest overrides this behavior to propagate the exception. + """ + testdir.makepyfile( + """ + import logging + + def test_bad_log(): + logging.warning('oops', 'first', 2) + """ + ) + result = testdir.runpytest() + result.assert_outcomes(failed=1) + result.stdout.fnmatch_lines( + [ + "====* FAILURES *====", + "*not all arguments converted during string formatting*", + ] + ) + + +def test_logging_emit_error_supressed(testdir: Testdir) -> None: + """If logging is configured to silently ignore errors, pytest + doesn't propagate errors either.""" + testdir.makepyfile( + """ + import logging + + def test_bad_log(monkeypatch): + monkeypatch.setattr(logging, 'raiseExceptions', False) + logging.warning('oops', 'first', 2) + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + +def test_log_file_cli_subdirectories_are_successfully_created(testdir): + path = testdir.makepyfile(""" def test_logger(): pass """) + expected = os.path.join(os.path.dirname(str(path)), "foo", "bar") + result = testdir.runpytest("--log-file=foo/bar/logf.log") + assert "logf.log" in os.listdir(expected) + assert result.ret == ExitCode.OK diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/.gitignore b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/.gitignore new file mode 100644 index 00000000000..d934447a03b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/.gitignore @@ -0,0 +1,2 @@ +*.html +assets/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/README.rst b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/README.rst new file mode 100644 index 00000000000..8f027c3bd35 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/README.rst @@ -0,0 +1,13 @@ +This folder contains tests and support files for smoke testing popular plugins against the current pytest version. + +The objective is to gauge if any intentional or unintentional changes in pytest break plugins. + +As a rule of thumb, we should add plugins here: + +1. That are used at large. This might be subjective in some cases, but if answer is yes to + the question: *if a new release of pytest causes pytest-X to break, will this break a ton of test suites out there?*. +2. That don't have large external dependencies: such as external services. + +Besides adding the plugin as dependency, we should also add a quick test which uses some +minimal part of the plugin, a smoke test. Also consider reusing one of the existing tests if that's +possible. diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.feature b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.feature new file mode 100644 index 00000000000..e404c4948e9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.feature @@ -0,0 +1,9 @@ +Feature: Buy things with apple + + Scenario: Buy fruits + Given A wallet with 50 + + When I buy some apples for 1 + And I buy some bananas for 2 + + Then I have 47 left diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py new file mode 100644 index 00000000000..35927ea5875 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py @@ -0,0 +1,39 @@ +from pytest_bdd import given +from pytest_bdd import scenario +from pytest_bdd import then +from pytest_bdd import when + +import pytest + + +@scenario("bdd_wallet.feature", "Buy fruits") +def test_publish(): + pass + + +@pytest.fixture +def wallet(): + class Wallet: + amount = 0 + + return Wallet() + + +@given("A wallet with 50") +def fill_wallet(wallet): + wallet.amount = 50 + + +@when("I buy some apples for 1") +def buy_apples(wallet): + wallet.amount -= 1 + + +@when("I buy some bananas for 2") +def buy_bananas(wallet): + wallet.amount -= 2 + + +@then("I have 47 left") +def check(wallet): + assert wallet.amount == 47 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/django_settings.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/django_settings.py new file mode 100644 index 00000000000..0715f476531 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/django_settings.py @@ -0,0 +1 @@ +SECRET_KEY = "mysecret" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini new file mode 100644 index 00000000000..f6c77b0dee5 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +addopts = --strict-markers +filterwarnings = + error::pytest.PytestWarning diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py new file mode 100644 index 00000000000..65c2f593663 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py @@ -0,0 +1,8 @@ +import anyio + +import pytest + + +@pytest.mark.anyio +async def test_sleep(): + await anyio.sleep(0) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py new file mode 100644 index 00000000000..5d2a3faccfc --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py @@ -0,0 +1,8 @@ +import asyncio + +import pytest + + +@pytest.mark.asyncio +async def test_sleep(): + await asyncio.sleep(0) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py new file mode 100644 index 00000000000..740469d00fb --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py @@ -0,0 +1,2 @@ +def test_mocker(mocker): + mocker.MagicMock() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py new file mode 100644 index 00000000000..199f7850bc4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py @@ -0,0 +1,8 @@ +import trio + +import pytest + + +@pytest.mark.trio +async def test_sleep(): + await trio.sleep(0) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py new file mode 100644 index 00000000000..94748d036e5 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py @@ -0,0 +1,18 @@ +import pytest_twisted +from twisted.internet.task import deferLater + + +def sleep(): + import twisted.internet.reactor + + return deferLater(clock=twisted.internet.reactor, delay=0) + + +@pytest_twisted.inlineCallbacks +def test_inlineCallbacks(): + yield sleep() + + +@pytest_twisted.ensureDeferred +async def test_inlineCallbacks_async(): + await sleep() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py new file mode 100644 index 00000000000..20b2fc4b5bb --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py @@ -0,0 +1,10 @@ +import pytest + + +def test_foo(): + assert True + + +@pytest.mark.parametrize("i", range(3)) +def test_bar(i): + assert True diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/approx.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/approx.py index cd8df5d274f..194423dc3b0 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/approx.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/approx.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- -import doctest import operator -import sys from decimal import Decimal from fractions import Fraction from operator import eq from operator import ne +from typing import Optional import pytest from pytest import approx @@ -13,68 +11,81 @@ from pytest import approx inf, nan = float("inf"), float("nan") -class MyDocTestRunner(doctest.DocTestRunner): - def __init__(self): - doctest.DocTestRunner.__init__(self) +@pytest.fixture +def mocked_doctest_runner(monkeypatch): + import doctest - def report_failure(self, out, test, example, got): - raise AssertionError( - "'{}' evaluates to '{}', not '{}'".format( - example.source.strip(), got.strip(), example.want.strip() + class MockedPdb: + def __init__(self, out): + pass + + def set_trace(self): + raise NotImplementedError("not used") + + def reset(self): + pass + + def set_continue(self): + pass + + monkeypatch.setattr("doctest._OutputRedirectingPdb", MockedPdb) + + class MyDocTestRunner(doctest.DocTestRunner): + def report_failure(self, out, test, example, got): + raise AssertionError( + "'{}' evaluates to '{}', not '{}'".format( + example.source.strip(), got.strip(), example.want.strip() + ) ) - ) + return MyDocTestRunner() -class TestApprox(object): - @pytest.fixture - def plus_minus(self): - return u"\u00b1" if sys.version_info[0] > 2 else u"+-" - def test_repr_string(self, plus_minus): - tol1, tol2, infr = "1.0e-06", "2.0e-06", "inf" - assert repr(approx(1.0)) == "1.0 {pm} {tol1}".format(pm=plus_minus, tol1=tol1) - assert repr( - approx([1.0, 2.0]) - ) == "approx([1.0 {pm} {tol1}, 2.0 {pm} {tol2}])".format( - pm=plus_minus, tol1=tol1, tol2=tol2 - ) - assert repr( - approx((1.0, 2.0)) - ) == "approx((1.0 {pm} {tol1}, 2.0 {pm} {tol2}))".format( - pm=plus_minus, tol1=tol1, tol2=tol2 - ) +class TestApprox: + def test_repr_string(self): + assert repr(approx(1.0)) == "1.0 ± 1.0e-06" + assert repr(approx([1.0, 2.0])) == "approx([1.0 ± 1.0e-06, 2.0 ± 2.0e-06])" + assert repr(approx((1.0, 2.0))) == "approx((1.0 ± 1.0e-06, 2.0 ± 2.0e-06))" assert repr(approx(inf)) == "inf" - assert repr(approx(1.0, rel=nan)) == "1.0 {pm} ???".format(pm=plus_minus) - assert repr(approx(1.0, rel=inf)) == "1.0 {pm} {infr}".format( - pm=plus_minus, infr=infr - ) - assert repr(approx(1.0j, rel=inf)) == "1j" + assert repr(approx(1.0, rel=nan)) == "1.0 ± ???" + assert repr(approx(1.0, rel=inf)) == "1.0 ± inf" # Dictionaries aren't ordered, so we need to check both orders. assert repr(approx({"a": 1.0, "b": 2.0})) in ( - "approx({{'a': 1.0 {pm} {tol1}, 'b': 2.0 {pm} {tol2}}})".format( - pm=plus_minus, tol1=tol1, tol2=tol2 - ), - "approx({{'b': 2.0 {pm} {tol2}, 'a': 1.0 {pm} {tol1}}})".format( - pm=plus_minus, tol1=tol1, tol2=tol2 - ), + "approx({'a': 1.0 ± 1.0e-06, 'b': 2.0 ± 2.0e-06})", + "approx({'b': 2.0 ± 2.0e-06, 'a': 1.0 ± 1.0e-06})", ) + def test_repr_complex_numbers(self): + assert repr(approx(inf + 1j)) == "(inf+1j)" + assert repr(approx(1.0j, rel=inf)) == "1j ± inf" + + # can't compute a sensible tolerance + assert repr(approx(nan + 1j)) == "(nan+1j) ± ???" + + assert repr(approx(1.0j)) == "1j ± 1.0e-06 ∠ ±180°" + + # relative tolerance is scaled to |3+4j| = 5 + assert repr(approx(3 + 4 * 1j)) == "(3+4j) ± 5.0e-06 ∠ ±180°" + + # absolute tolerance is not scaled + assert repr(approx(3.3 + 4.4 * 1j, abs=0.02)) == "(3.3+4.4j) ± 2.0e-02 ∠ ±180°" + @pytest.mark.parametrize( - "value, repr_string", + "value, expected_repr_string", [ - (5.0, "approx(5.0 {pm} 5.0e-06)"), - ([5.0], "approx([5.0 {pm} 5.0e-06])"), - ([[5.0]], "approx([[5.0 {pm} 5.0e-06]])"), - ([[5.0, 6.0]], "approx([[5.0 {pm} 5.0e-06, 6.0 {pm} 6.0e-06]])"), - ([[5.0], [6.0]], "approx([[5.0 {pm} 5.0e-06], [6.0 {pm} 6.0e-06]])"), + (5.0, "approx(5.0 ± 5.0e-06)"), + ([5.0], "approx([5.0 ± 5.0e-06])"), + ([[5.0]], "approx([[5.0 ± 5.0e-06]])"), + ([[5.0, 6.0]], "approx([[5.0 ± 5.0e-06, 6.0 ± 6.0e-06]])"), + ([[5.0], [6.0]], "approx([[5.0 ± 5.0e-06], [6.0 ± 6.0e-06]])"), ], ) - def test_repr_nd_array(self, plus_minus, value, repr_string): + def test_repr_nd_array(self, value, expected_repr_string): """Make sure that arrays of all different dimensions are repr'd correctly.""" np = pytest.importorskip("numpy") np_array = np.array(value) - assert repr(approx(np_array)) == repr_string.format(pm=plus_minus) + assert repr(approx(np_array)) == expected_repr_string def test_operator_overloading(self): assert 1 == approx(1, rel=1e-6, abs=1e-12) @@ -111,18 +122,22 @@ class TestApprox(object): assert a == approx(x, rel=5e-1, abs=0.0) assert a != approx(x, rel=5e-2, abs=0.0) - def test_negative_tolerance(self): + @pytest.mark.parametrize( + ("rel", "abs"), + [ + (-1e100, None), + (None, -1e100), + (1e100, -1e100), + (-1e100, 1e100), + (-1e100, -1e100), + ], + ) + def test_negative_tolerance( + self, rel: Optional[float], abs: Optional[float] + ) -> None: # Negative tolerances are not allowed. - illegal_kwargs = [ - dict(rel=-1e100), - dict(abs=-1e100), - dict(rel=1e100, abs=-1e100), - dict(rel=-1e100, abs=1e100), - dict(rel=-1e100, abs=-1e100), - ] - for kwargs in illegal_kwargs: - with pytest.raises(ValueError): - 1.1 == approx(1, **kwargs) + with pytest.raises(ValueError): + 1.1 == approx(1, rel, abs) def test_inf_tolerance(self): # Everything should be equal if the tolerance is infinite. @@ -133,19 +148,21 @@ class TestApprox(object): assert a == approx(x, rel=0.0, abs=inf) assert a == approx(x, rel=inf, abs=inf) - def test_inf_tolerance_expecting_zero(self): + def test_inf_tolerance_expecting_zero(self) -> None: # If the relative tolerance is zero but the expected value is infinite, # the actual tolerance is a NaN, which should be an error. - illegal_kwargs = [dict(rel=inf, abs=0.0), dict(rel=inf, abs=inf)] - for kwargs in illegal_kwargs: - with pytest.raises(ValueError): - 1 == approx(0, **kwargs) - - def test_nan_tolerance(self): - illegal_kwargs = [dict(rel=nan), dict(abs=nan), dict(rel=nan, abs=nan)] - for kwargs in illegal_kwargs: - with pytest.raises(ValueError): - 1.1 == approx(1, **kwargs) + with pytest.raises(ValueError): + 1 == approx(0, rel=inf, abs=0.0) + with pytest.raises(ValueError): + 1 == approx(0, rel=inf, abs=inf) + + def test_nan_tolerance(self) -> None: + with pytest.raises(ValueError): + 1.1 == approx(1, rel=nan) + with pytest.raises(ValueError): + 1.1 == approx(1, abs=nan) + with pytest.raises(ValueError): + 1.1 == approx(1, rel=nan, abs=nan) def test_reasonable_defaults(self): # Whatever the defaults are, they should work for numbers close to 1 @@ -418,13 +435,15 @@ class TestApprox(object): assert a12 != approx(a21) assert a21 != approx(a12) - def test_doctests(self): + def test_doctests(self, mocked_doctest_runner) -> None: + import doctest + parser = doctest.DocTestParser() + assert approx.__doc__ is not None test = parser.get_doctest( approx.__doc__, {"approx": approx}, approx.__name__, None, None ) - runner = MyDocTestRunner() - runner.run(test) + mocked_doctest_runner.run(test) def test_unicode_plus_minus(self, testdir): """ @@ -441,7 +460,7 @@ class TestApprox(object): expected = "4.0e-06" result = testdir.runpytest() result.stdout.fnmatch_lines( - ["*At index 0 diff: 3 != 4 * {}".format(expected), "=* 1 failed in *="] + ["*At index 0 diff: 3 != 4 ± {}".format(expected), "=* 1 failed in *="] ) @pytest.mark.parametrize( @@ -469,9 +488,7 @@ class TestApprox(object): ], ) def test_comparison_operator_type_error(self, op): - """ - pytest.approx should raise TypeError for operators other than == and != (#2003). - """ + """pytest.approx should raise TypeError for operators other than == and != (#2003).""" with pytest.raises(TypeError): op(1, approx(1, rel=1e-6, abs=1e-12)) @@ -498,7 +515,7 @@ class TestApprox(object): assert approx(expected, rel=5e-8, abs=0) != actual def test_generic_sized_iterable_object(self): - class MySizedIterable(object): + class MySizedIterable: def __iter__(self): return iter([1, 2, 3, 4]) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/collect.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/collect.py index 501b30a49b0..01294039860 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/collect.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/collect.py @@ -1,15 +1,16 @@ -# -*- coding: utf-8 -*- -import os import sys import textwrap +from typing import Any +from typing import Dict import _pytest._code import pytest -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.config import ExitCode from _pytest.nodes import Collector +from _pytest.pytester import Testdir -class TestModule(object): +class TestModule: def test_failing_import(self, testdir): modcol = testdir.getmodulecol("import alksdjalskdjalkjals") pytest.raises(Collector.CollectError, modcol.collect) @@ -69,7 +70,7 @@ class TestModule(object): def test_invalid_test_module_name(self, testdir): a = testdir.mkdir("a") a.ensure("test_one.part1.py") - result = testdir.runpytest("-rw") + result = testdir.runpytest() result.stdout.fnmatch_lines( [ "ImportError while importing test module*test_one.part1*", @@ -107,22 +108,16 @@ class TestModule(object): assert result.ret == 2 stdout = result.stdout.str() - for name in ("_pytest", os.path.join("py", "_path")): - if verbose == 2: - assert name in stdout - else: - assert name not in stdout + if verbose == 2: + assert "_pytest" in stdout + else: + assert "_pytest" not in stdout def test_show_traceback_import_error_unicode(self, testdir): """Check test modules collected which raise ImportError with unicode messages are handled properly (#2336). """ - testdir.makepyfile( - u""" - # -*- coding: utf-8 -*- - raise ImportError(u'Something bad happened ☺') - """ - ) + testdir.makepyfile("raise ImportError('Something bad happened ☺')") result = testdir.runpytest() result.stdout.fnmatch_lines( [ @@ -134,7 +129,7 @@ class TestModule(object): assert result.ret == 2 -class TestClass(object): +class TestClass: def test_class_with_init_warning(self, testdir): testdir.makepyfile( """ @@ -143,7 +138,7 @@ class TestClass(object): pass """ ) - result = testdir.runpytest("-rw") + result = testdir.runpytest() result.stdout.fnmatch_lines( [ "*cannot collect test class 'TestClass1' because it has " @@ -159,7 +154,7 @@ class TestClass(object): pass """ ) - result = testdir.runpytest("-rw") + result = testdir.runpytest() result.stdout.fnmatch_lines( [ "*cannot collect test class 'TestClass1' because it has " @@ -236,7 +231,7 @@ class TestClass(object): TestCase = collections.namedtuple('TestCase', ['a']) """ ) - result = testdir.runpytest("-rw") + result = testdir.runpytest() result.stdout.fnmatch_lines( "*cannot collect test class 'TestCase' " "because it has a __new__ constructor*" @@ -252,10 +247,10 @@ class TestClass(object): """ ) result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED -class TestFunction(object): +class TestFunction: def test_getmodulecollector(self, testdir): item = testdir.getitem("def test_func(): pass") modcol = item.getparent(pytest.Module) @@ -287,21 +282,23 @@ class TestFunction(object): from _pytest.fixtures import FixtureManager config = testdir.parseconfigure() - session = testdir.Session(config) + session = testdir.Session.from_config(config) session._fixturemanager = FixtureManager(session) - return pytest.Function(config=config, parent=session, **kwargs) + return pytest.Function.from_parent(parent=session, **kwargs) - def test_function_equality(self, testdir, tmpdir): + def test_function_equality(self, testdir): def func1(): pass def func2(): pass - f1 = self.make_function(testdir, name="name", args=(1,), callobj=func1) + f1 = self.make_function(testdir, name="name", callobj=func1) assert f1 == f1 - f2 = self.make_function(testdir, name="name", callobj=func2) + f2 = self.make_function( + testdir, name="name", callobj=func2, originalname="foobar" + ) assert f1 != f2 def test_repr_produces_actual_test_id(self, testdir): @@ -498,7 +495,20 @@ class TestFunction(object): ) assert "foo" in keywords[1] and "bar" in keywords[1] and "baz" in keywords[1] - def test_function_equality_with_callspec(self, testdir, tmpdir): + def test_parametrize_with_empty_string_arguments(self, testdir): + items = testdir.getitems( + """\ + import pytest + + @pytest.mark.parametrize('v', ('', ' ')) + @pytest.mark.parametrize('w', ('', ' ')) + def test(v, w): ... + """ + ) + names = {item.name for item in items} + assert names == {"test[-]", "test[ -]", "test[- ]", "test[ - ]"} + + def test_function_equality_with_callspec(self, testdir): items = testdir.getitems( """ import pytest @@ -514,12 +524,12 @@ class TestFunction(object): item = testdir.getitem("def test_func(): raise ValueError") config = item.config - class MyPlugin1(object): - def pytest_pyfunc_call(self, pyfuncitem): + class MyPlugin1: + def pytest_pyfunc_call(self): raise ValueError - class MyPlugin2(object): - def pytest_pyfunc_call(self, pyfuncitem): + class MyPlugin2: + def pytest_pyfunc_call(self): return True config.pluginmanager.register(MyPlugin1()) @@ -652,20 +662,47 @@ class TestFunction(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 3 passed in *"]) - def test_function_original_name(self, testdir): + def test_function_originalname(self, testdir: Testdir) -> None: items = testdir.getitems( """ import pytest + @pytest.mark.parametrize('arg', [1,2]) def test_func(arg): pass + + def test_no_param(): + pass """ ) - assert [x.originalname for x in items] == ["test_func", "test_func"] + originalnames = [] + for x in items: + assert isinstance(x, pytest.Function) + originalnames.append(x.originalname) + assert originalnames == [ + "test_func", + "test_func", + "test_no_param", + ] + + def test_function_with_square_brackets(self, testdir: Testdir) -> None: + """Check that functions with square brackets don't cause trouble.""" + p1 = testdir.makepyfile( + """ + locals()["test_foo[name]"] = lambda: None + """ + ) + result = testdir.runpytest("-v", str(p1)) + result.stdout.fnmatch_lines( + [ + "test_function_with_square_brackets.py::test_foo[[]name[]] PASSED *", + "*= 1 passed in *", + ] + ) -class TestSorting(object): - def test_check_equality(self, testdir): +class TestSorting: + def test_check_equality(self, testdir) -> None: modcol = testdir.getmodulecol( """ def test_pass(): pass @@ -679,8 +716,6 @@ class TestSorting(object): assert fn1 == fn2 assert fn1 != modcol - if sys.version_info < (3, 0): - assert cmp(fn1, fn2) == 0 # NOQA assert hash(fn1) == hash(fn2) fn3 = testdir.collect_by_name(modcol, "test_fail") @@ -689,10 +724,10 @@ class TestSorting(object): assert fn1 != fn3 for fn in fn1, fn2, fn3: - assert fn != 3 + assert fn != 3 # type: ignore[comparison-overlap] assert fn != modcol - assert fn != [1, 2, 3] - assert [1, 2, 3] != fn + assert fn != [1, 2, 3] # type: ignore[comparison-overlap] + assert [1, 2, 3] != fn # type: ignore[comparison-overlap] assert modcol != fn def test_allow_sane_sorting_for_decorators(self, testdir): @@ -718,7 +753,7 @@ class TestSorting(object): assert [item.name for item in colitems] == ["test_b", "test_a"] -class TestConftestCustomization(object): +class TestConftestCustomization: def test_pytest_pycollect_module(self, testdir): testdir.makeconftest( """ @@ -727,7 +762,7 @@ class TestConftestCustomization(object): pass def pytest_pycollect_makemodule(path, parent): if path.basename == "test_xyz.py": - return MyModule(path, parent) + return MyModule.from_parent(fspath=path, parent=parent) """ ) testdir.makepyfile("def test_some(): pass") @@ -801,22 +836,13 @@ class TestConftestCustomization(object): pass def pytest_pycollect_makeitem(collector, name, obj): if name == "some": - return MyFunction(name, collector) + return MyFunction.from_parent(name=name, parent=collector) """ ) testdir.makepyfile("def some(): pass") result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines(["*MyFunction*some*"]) - def test_makeitem_non_underscore(self, testdir, monkeypatch): - modcol = testdir.getmodulecol("def _hello(): pass") - values = [] - monkeypatch.setattr( - pytest.Module, "_makeitem", lambda self, name, obj: values.append(name) - ) - values = modcol.collect() - assert "_hello" not in values - def test_issue2369_collect_module_fileext(self, testdir): """Ensure we can collect files with weird file extensions as Python modules (#2369)""" @@ -838,7 +864,7 @@ class TestConftestCustomization(object): def pytest_collect_file(path, parent): if path.ext == ".narf": - return Module(path, parent)""" + return Module.from_parent(fspath=path, parent=parent)""" ) testdir.makefile( ".narf", @@ -850,6 +876,34 @@ class TestConftestCustomization(object): result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines(["*1 passed*"]) + def test_early_ignored_attributes(self, testdir: Testdir) -> None: + """Builtin attributes should be ignored early on, even if + configuration would otherwise allow them. + + This tests a performance optimization, not correctness, really, + although it tests PytestCollectionWarning is not raised, while + it would have been raised otherwise. + """ + testdir.makeini( + """ + [pytest] + python_classes=* + python_functions=* + """ + ) + testdir.makepyfile( + """ + class TestEmpty: + pass + test_empty = TestEmpty() + def test_real(): + pass + """ + ) + items, rec = testdir.inline_genitems() + assert rec.ret == 0 + assert len(items) == 1 + def test_setup_only_available_in_subdir(testdir): sub1 = testdir.mkpydir("sub1") @@ -893,12 +947,14 @@ def test_modulecol_roundtrip(testdir): assert modcol.name == newcol.name -class TestTracebackCutting(object): +class TestTracebackCutting: def test_skip_simple(self): with pytest.raises(pytest.skip.Exception) as excinfo: pytest.skip("xxx") assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].ishidden() + assert excinfo.traceback[-2].frame.code.name == "test_skip_simple" + assert not excinfo.traceback[-2].ishidden() def test_traceback_argsetup(self, testdir): testdir.makeconftest( @@ -947,8 +1003,7 @@ class TestTracebackCutting(object): result.stdout.fnmatch_lines([">*asd*", "E*NameError*"]) def test_traceback_filter_error_during_fixture_collection(self, testdir): - """integration test for issue #995. - """ + """Integration test for issue #995.""" testdir.makepyfile( """ import pytest @@ -973,34 +1028,37 @@ class TestTracebackCutting(object): assert "INTERNALERROR>" not in out result.stdout.fnmatch_lines(["*ValueError: fail me*", "* 1 error in *"]) - def test_filter_traceback_generated_code(self): - """test that filter_traceback() works with the fact that + def test_filter_traceback_generated_code(self) -> None: + """Test that filter_traceback() works with the fact that _pytest._code.code.Code.path attribute might return an str object. + In this case, one of the entries on the traceback was produced by dynamically generated code. See: https://bitbucket.org/pytest-dev/py/issues/71 This fixes #995. """ - from _pytest.python import filter_traceback + from _pytest._code import filter_traceback try: - ns = {} + ns = {} # type: Dict[str, Any] exec("def foo(): raise ValueError", ns) ns["foo"]() except ValueError: _, _, tb = sys.exc_info() - tb = _pytest._code.Traceback(tb) - assert isinstance(tb[-1].path, str) - assert not filter_traceback(tb[-1]) + assert tb is not None + traceback = _pytest._code.Traceback(tb) + assert isinstance(traceback[-1].path, str) + assert not filter_traceback(traceback[-1]) - def test_filter_traceback_path_no_longer_valid(self, testdir): - """test that filter_traceback() works with the fact that + def test_filter_traceback_path_no_longer_valid(self, testdir) -> None: + """Test that filter_traceback() works with the fact that _pytest._code.code.Code.path attribute might return an str object. + In this case, one of the files in the traceback no longer exists. This fixes #1133. """ - from _pytest.python import filter_traceback + from _pytest._code import filter_traceback testdir.syspathinsert() testdir.makepyfile( @@ -1016,14 +1074,15 @@ class TestTracebackCutting(object): except ValueError: _, _, tb = sys.exc_info() + assert tb is not None testdir.tmpdir.join("filter_traceback_entry_as_str.py").remove() - tb = _pytest._code.Traceback(tb) - assert isinstance(tb[-1].path, str) - assert filter_traceback(tb[-1]) + traceback = _pytest._code.Traceback(tb) + assert isinstance(traceback[-1].path, str) + assert filter_traceback(traceback[-1]) -class TestReportInfo(object): - def test_itemreport_reportinfo(self, testdir, linecomp): +class TestReportInfo: + def test_itemreport_reportinfo(self, testdir): testdir.makeconftest( """ import pytest @@ -1032,7 +1091,7 @@ class TestReportInfo(object): return "ABCDE", 42, "custom" def pytest_pycollect_makeitem(collector, name, obj): if name == "test_func": - return MyFunction(name, parent=collector) + return MyFunction.from_parent(name=name, parent=collector) """ ) item = testdir.getitem("def test_func(): pass") @@ -1147,54 +1206,8 @@ def test_unorderable_types(testdir): """ ) result = testdir.runpytest() - assert "TypeError" not in result.stdout.str() - assert result.ret == EXIT_NOTESTSCOLLECTED - - -def test_collect_functools_partial(testdir): - """ - Test that collection of functools.partial object works, and arguments - to the wrapped functions are dealt correctly (see #811). - """ - testdir.makepyfile( - """ - import functools - import pytest - - @pytest.fixture - def fix1(): - return 'fix1' - - @pytest.fixture - def fix2(): - return 'fix2' - - def check1(i, fix1): - assert i == 2 - assert fix1 == 'fix1' - - def check2(fix1, i): - assert i == 2 - assert fix1 == 'fix1' - - def check3(fix1, i, fix2): - assert i == 2 - assert fix1 == 'fix1' - assert fix2 == 'fix2' - - test_ok_1 = functools.partial(check1, i=2) - test_ok_2 = functools.partial(check1, i=2, fix1='fix1') - test_ok_3 = functools.partial(check1, 2) - test_ok_4 = functools.partial(check2, i=2) - test_ok_5 = functools.partial(check3, i=2) - test_ok_6 = functools.partial(check3, i=2, fix1='fix1') - - test_fail_1 = functools.partial(check2, 2) - test_fail_2 = functools.partial(check3, 2) - """ - ) - result = testdir.inline_run() - result.assertoutcome(passed=6, failed=2) + result.stdout.no_fnmatch_line("*TypeError*") + assert result.ret == ExitCode.NO_TESTS_COLLECTED @pytest.mark.filterwarnings("default") @@ -1202,7 +1215,7 @@ def test_dont_collect_non_function_callable(testdir): """Test for issue https://github.com/pytest-dev/pytest/issues/331 In this case an INTERNALERROR occurred trying to report the failure of - a test like this one because py test failed to get the source lines. + a test like this one because pytest failed to get the source lines. """ testdir.makepyfile( """ @@ -1216,12 +1229,12 @@ def test_dont_collect_non_function_callable(testdir): pass """ ) - result = testdir.runpytest("-rw") + result = testdir.runpytest() result.stdout.fnmatch_lines( [ "*collected 1 item*", "*test_dont_collect_non_function_callable.py:2: *cannot collect 'test_a' because it is not a function*", - "*1 passed, 1 warnings in *", + "*1 passed, 1 warning in *", ] ) @@ -1257,19 +1270,31 @@ def test_class_injection_does_not_break_collection(testdir): def test_syntax_error_with_non_ascii_chars(testdir): - """Fix decoding issue while formatting SyntaxErrors during collection (#578) - """ - testdir.makepyfile( - u""" - # -*- coding: utf-8 -*- - - ☃ - """ - ) + """Fix decoding issue while formatting SyntaxErrors during collection (#578).""" + testdir.makepyfile("☃") result = testdir.runpytest() result.stdout.fnmatch_lines(["*ERROR collecting*", "*SyntaxError*", "*1 error in*"]) +def test_collect_error_with_fulltrace(testdir): + testdir.makepyfile("assert 0") + result = testdir.runpytest("--fulltrace") + result.stdout.fnmatch_lines( + [ + "collected 0 items / 1 error", + "", + "*= ERRORS =*", + "*_ ERROR collecting test_collect_error_with_fulltrace.py _*", + "", + "> assert 0", + "E assert 0", + "", + "test_collect_error_with_fulltrace.py:1: AssertionError", + "*! Interrupted: 1 error during collection !*", + ] + ) + + def test_skip_duplicates_by_default(testdir): """Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py index 4869431c639..9ae5a91db43 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/fixtures.py @@ -1,51 +1,90 @@ -# -*- coding: utf-8 -*- import sys import textwrap import pytest from _pytest import fixtures -from _pytest.fixtures import FixtureLookupError +from _pytest.compat import getfuncargnames +from _pytest.config import ExitCode from _pytest.fixtures import FixtureRequest from _pytest.pathlib import Path from _pytest.pytester import get_public_names -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG -def test_getfuncargnames(): +def test_getfuncargnames_functions(): + """Test getfuncargnames for normal functions""" + def f(): - pass + raise NotImplementedError() - assert not fixtures.getfuncargnames(f) + assert not getfuncargnames(f) def g(arg): - pass + raise NotImplementedError() - assert fixtures.getfuncargnames(g) == ("arg",) + assert getfuncargnames(g) == ("arg",) def h(arg1, arg2="hello"): - pass + raise NotImplementedError() + + assert getfuncargnames(h) == ("arg1",) + + def j(arg1, arg2, arg3="hello"): + raise NotImplementedError() - assert fixtures.getfuncargnames(h) == ("arg1",) + assert getfuncargnames(j) == ("arg1", "arg2") - def h(arg1, arg2, arg3="hello"): - pass - assert fixtures.getfuncargnames(h) == ("arg1", "arg2") +def test_getfuncargnames_methods(): + """Test getfuncargnames for normal methods""" - class A(object): + class A: def f(self, arg1, arg2="hello"): - pass + raise NotImplementedError() + + assert getfuncargnames(A().f) == ("arg1",) + +def test_getfuncargnames_staticmethod(): + """Test getfuncargnames for staticmethods""" + + class A: @staticmethod - def static(arg1, arg2): - pass + def static(arg1, arg2, x=1): + raise NotImplementedError() + + assert getfuncargnames(A.static, cls=A) == ("arg1", "arg2") + - assert fixtures.getfuncargnames(A().f) == ("arg1",) - assert fixtures.getfuncargnames(A.static, cls=A) == ("arg1", "arg2") +def test_getfuncargnames_partial(): + """Check getfuncargnames for methods defined with functools.partial (#5701)""" + import functools + + def check(arg1, arg2, i): + raise NotImplementedError() + + class T: + test_ok = functools.partial(check, i=2) + + values = getfuncargnames(T().test_ok, name="test_ok") + assert values == ("arg1", "arg2") + + +def test_getfuncargnames_staticmethod_partial(): + """Check getfuncargnames for staticmethods defined with functools.partial (#5701)""" + import functools + + def check(arg1, arg2, i): + raise NotImplementedError() + + class T: + test_ok = staticmethod(functools.partial(check, i=2)) + + values = getfuncargnames(T().test_ok, name="test_ok") + assert values == ("arg1", "arg2") @pytest.mark.pytester_example_path("fixtures/fill_fixtures") -class TestFillFixtures(object): +class TestFillFixtures: def test_fillfuncargs_exposed(self): # used by oejskit, kept for compatibility assert pytest._fillfuncargs == fixtures.fillfixtures @@ -72,7 +111,7 @@ class TestFillFixtures(object): def test_funcarg_basic(self, testdir): testdir.copy_example() item = testdir.getitem(Path("test_funcarg_basic.py")) - fixtures.fillfixtures(item) + item._request._fillfixtures() del item.funcargs["request"] assert len(get_public_names(item.funcargs)) == 2 assert item.funcargs["some"] == "test_func" @@ -104,14 +143,14 @@ class TestFillFixtures(object): p = testdir.copy_example() result = testdir.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest(next(p.visit("test_*.py"))) + result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py")))) result.stdout.fnmatch_lines(["*1 passed*"]) def test_extend_fixture_conftest_conftest(self, testdir): p = testdir.copy_example() result = testdir.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - result = testdir.runpytest(next(p.visit("test_*.py"))) + result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py")))) result.stdout.fnmatch_lines(["*1 passed*"]) def test_extend_fixture_conftest_plugin(self, testdir): @@ -357,6 +396,132 @@ class TestFillFixtures(object): result = testdir.runpytest(testfile) result.stdout.fnmatch_lines(["*3 passed*"]) + def test_override_fixture_reusing_super_fixture_parametrization(self, testdir): + """Override a fixture at a lower level, reusing the higher-level fixture that + is parametrized (#1953). + """ + testdir.makeconftest( + """ + import pytest + + @pytest.fixture(params=[1, 2]) + def foo(request): + return request.param + """ + ) + testdir.makepyfile( + """ + import pytest + + @pytest.fixture + def foo(foo): + return foo * 2 + + def test_spam(foo): + assert foo in (2, 4) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*2 passed*"]) + + def test_override_parametrize_fixture_and_indirect(self, testdir): + """Override a fixture at a lower level, reusing the higher-level fixture that + is parametrized, while also using indirect parametrization. + """ + testdir.makeconftest( + """ + import pytest + + @pytest.fixture(params=[1, 2]) + def foo(request): + return request.param + """ + ) + testdir.makepyfile( + """ + import pytest + + @pytest.fixture + def foo(foo): + return foo * 2 + + @pytest.fixture + def bar(request): + return request.param * 100 + + @pytest.mark.parametrize("bar", [42], indirect=True) + def test_spam(bar, foo): + assert bar == 4200 + assert foo in (2, 4) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*2 passed*"]) + + def test_override_top_level_fixture_reusing_super_fixture_parametrization( + self, testdir + ): + """Same as the above test, but with another level of overwriting.""" + testdir.makeconftest( + """ + import pytest + + @pytest.fixture(params=['unused', 'unused']) + def foo(request): + return request.param + """ + ) + testdir.makepyfile( + """ + import pytest + + @pytest.fixture(params=[1, 2]) + def foo(request): + return request.param + + class Test: + + @pytest.fixture + def foo(self, foo): + return foo * 2 + + def test_spam(self, foo): + assert foo in (2, 4) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*2 passed*"]) + + def test_override_parametrized_fixture_with_new_parametrized_fixture(self, testdir): + """Overriding a parametrized fixture, while also parametrizing the new fixture and + simultaneously requesting the overwritten fixture as parameter, yields the same value + as ``request.param``. + """ + testdir.makeconftest( + """ + import pytest + + @pytest.fixture(params=['ignored', 'ignored']) + def foo(request): + return request.param + """ + ) + testdir.makepyfile( + """ + import pytest + + @pytest.fixture(params=[10, 20]) + def foo(foo, request): + assert request.param == foo + return foo * 2 + + def test_spam(foo): + assert foo in (20, 40) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*2 passed*"]) + def test_autouse_fixture_plugin(self, testdir): # A fixture from a plugin has no baseid set, which screwed up # the autouse fixture handling. @@ -411,12 +576,13 @@ class TestFillFixtures(object): "*ERROR at setup of test_lookup_error*", " def test_lookup_error(unknown):*", "E fixture 'unknown' not found", - "> available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*", # sorted + "> available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*", + # sorted "> use 'py*test --fixtures *' for help on them.", "*1 error*", ] ) - assert "INTERNAL" not in result.stdout.str() + result.stdout.no_fnmatch_line("*INTERNAL*") def test_fixture_excinfo_leak(self, testdir): # on python2 sys.excinfo would leak into fixture executions @@ -443,7 +609,7 @@ class TestFillFixtures(object): assert result.ret == 0 -class TestRequestBasic(object): +class TestRequestBasic: def test_request_attributes(self, testdir): item = testdir.getitem( """ @@ -600,8 +766,7 @@ class TestRequestBasic(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 2 passed in *"]) - @pytest.mark.parametrize("getfixmethod", ("getfixturevalue", "getfuncargvalue")) - def test_getfixturevalue(self, testdir, getfixmethod): + def test_getfixturevalue(self, testdir): item = testdir.getitem( """ import pytest @@ -614,35 +779,22 @@ class TestRequestBasic(object): def test_func(something): pass """ ) - import contextlib - - if getfixmethod == "getfuncargvalue": - warning_expectation = pytest.warns(DeprecationWarning) - else: - # see #1830 for a cleaner way to accomplish this - @contextlib.contextmanager - def expecting_no_warning(): - yield - - warning_expectation = expecting_no_warning() - req = item._request - with warning_expectation: - fixture_fetcher = getattr(req, getfixmethod) - with pytest.raises(FixtureLookupError): - fixture_fetcher("notexists") - val = fixture_fetcher("something") - assert val == 1 - val = fixture_fetcher("something") - assert val == 1 - val2 = fixture_fetcher("other") - assert val2 == 2 - val2 = fixture_fetcher("other") # see about caching - assert val2 == 2 - pytest._fillfuncargs(item) - assert item.funcargs["something"] == 1 - assert len(get_public_names(item.funcargs)) == 2 - assert "request" in item.funcargs + + with pytest.raises(pytest.FixtureLookupError): + req.getfixturevalue("notexists") + val = req.getfixturevalue("something") + assert val == 1 + val = req.getfixturevalue("something") + assert val == 1 + val2 = req.getfixturevalue("other") + assert val2 == 2 + val2 = req.getfixturevalue("other") # see about caching + assert val2 == 2 + item._request._fillfixtures() + assert item.funcargs["something"] == 1 + assert len(get_public_names(item.funcargs)) == 2 + assert "request" in item.funcargs def test_request_addfinalizer(self, testdir): item = testdir.getitem( @@ -656,7 +808,7 @@ class TestRequestBasic(object): """ ) item.session._setupstate.prepare(item) - pytest._fillfuncargs(item) + item._request._fillfixtures() # successively check finalization calls teardownlist = item.getparent(pytest.Module).obj.teardownlist ss = item.session._setupstate @@ -789,25 +941,6 @@ class TestRequestBasic(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) - def test_funcargnames_compatattr(self, testdir): - testdir.makepyfile( - """ - import pytest - def pytest_generate_tests(metafunc): - assert metafunc.funcargnames == metafunc.fixturenames - @pytest.fixture - def fn(request): - assert request._pyfuncitem.funcargnames == \ - request._pyfuncitem.fixturenames - return request.funcargnames, request.fixturenames - - def test_hello(fn): - assert fn[0] == fn[1] - """ - ) - reprec = testdir.inline_run() - reprec.assertoutcome(passed=1) - def test_setupdecorator_and_xunit(self, testdir): testdir.makepyfile( """ @@ -902,7 +1035,7 @@ class TestRequestBasic(object): reprec.assertoutcome(passed=2) -class TestRequestMarking(object): +class TestRequestMarking: def test_applymarker(self, testdir): item1, item2 = testdir.getitems( """ @@ -972,7 +1105,7 @@ class TestRequestMarking(object): reprec.assertoutcome(passed=2) -class TestFixtureUsages(object): +class TestFixtureUsages: def test_noargfixturedec(self, testdir): testdir.makepyfile( """ @@ -1074,6 +1207,38 @@ class TestFixtureUsages(object): "*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'" ) + @pytest.mark.parametrize("scope", ["function", "session"]) + def test_parameters_without_eq_semantics(self, scope, testdir): + testdir.makepyfile( + """ + class NoEq1: # fails on `a == b` statement + def __eq__(self, _): + raise RuntimeError + + class NoEq2: # fails on `if a == b:` statement + def __eq__(self, _): + class NoBool: + def __bool__(self): + raise RuntimeError + return NoBool() + + import pytest + @pytest.fixture(params=[NoEq1(), NoEq2()], scope={scope!r}) + def no_eq(request): + return request.param + + def test1(no_eq): + pass + + def test2(no_eq): + pass + """.format( + scope=scope + ) + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*4 passed*"]) + def test_funcarg_parametrized_and_used_twice(self, testdir): testdir.makepyfile( """ @@ -1139,22 +1304,6 @@ class TestFixtureUsages(object): values = reprec.getfailedcollections() assert len(values) == 1 - @pytest.mark.filterwarnings("ignore::pytest.PytestDeprecationWarning") - def test_request_can_be_overridden(self, testdir): - testdir.makepyfile( - """ - import pytest - @pytest.fixture() - def request(request): - request.a = 1 - return request - def test_request(request): - assert request.a == 1 - """ - ) - reprec = testdir.inline_run() - reprec.assertoutcome(passed=1) - def test_usefixtures_marker(self, testdir): testdir.makepyfile( """ @@ -1271,7 +1420,7 @@ class TestFixtureUsages(object): DB_INITIALIZED = None - @pytest.yield_fixture(scope="session", autouse=True) + @pytest.fixture(scope="session", autouse=True) def db(): global DB_INITIALIZED DB_INITIALIZED = True @@ -1303,7 +1452,7 @@ class TestFixtureUsages(object): result.stdout.fnmatch_lines(["* 2 passed in *"]) -class TestFixtureManagerParseFactories(object): +class TestFixtureManagerParseFactories: @pytest.fixture def testdir(self, request): testdir = request.getfixturevalue("testdir") @@ -1358,9 +1507,8 @@ class TestFixtureManagerParseFactories(object): def test_parsefactories_conftest_and_module_and_class(self, testdir): testdir.makepyfile( - """ + """\ import pytest - import six @pytest.fixture def hello(request): @@ -1377,7 +1525,7 @@ class TestFixtureManagerParseFactories(object): assert faclist[0].func(item._request) == "conftest" assert faclist[1].func(item._request) == "module" assert faclist[2].func(item._request) == "class" - """ + """ ) reprec = testdir.inline_run("-s") reprec.assertoutcome(passed=1) @@ -1529,7 +1677,7 @@ class TestFixtureManagerParseFactories(object): result.stdout.fnmatch_lines(["*passed*"]) -class TestAutouseDiscovery(object): +class TestAutouseDiscovery: @pytest.fixture def testdir(self, testdir): testdir.makeconftest( @@ -1640,10 +1788,8 @@ class TestAutouseDiscovery(object): reprec.assertoutcome(passed=2) def test_callables_nocode(self, testdir): - """ - an imported mock.call would break setup/factory discovery - due to it being callable and __code__ not being a code object - """ + """An imported mock.call would break setup/factory discovery due to + it being callable and __code__ not being a code object.""" testdir.makepyfile( """ class _call(tuple): @@ -1705,7 +1851,7 @@ class TestAutouseDiscovery(object): reprec.assertoutcome(passed=3) -class TestAutouseManagement(object): +class TestAutouseManagement: def test_autouse_conftest_mid_directory(self, testdir): pkgdir = testdir.mkpydir("xyz123") pkgdir.join("conftest.py").write( @@ -1851,7 +1997,9 @@ class TestAutouseManagement(object): reprec = testdir.inline_run("-v", "-s", confcut) reprec.assertoutcome(passed=8) config = reprec.getcalls("pytest_unconfigure")[0].config - values = config.pluginmanager._getconftestmodules(p)[0].values + values = config.pluginmanager._getconftestmodules(p, importmode="prepend")[ + 0 + ].values assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2 def test_scope_ordering(self, testdir): @@ -1953,7 +2101,7 @@ class TestAutouseManagement(object): reprec.assertoutcome(passed=2) -class TestFixtureMarker(object): +class TestFixtureMarker: def test_parametrize(self, testdir): testdir.makepyfile( """ @@ -2200,12 +2348,74 @@ class TestFixtureMarker(object): pass """ ) - result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) + result = testdir.runpytest() assert result.ret != 0 result.stdout.fnmatch_lines( ["*ScopeMismatch*You tried*function*session*request*"] ) + def test_dynamic_scope(self, testdir): + testdir.makeconftest( + """ + import pytest + + + def pytest_addoption(parser): + parser.addoption("--extend-scope", action="store_true", default=False) + + + def dynamic_scope(fixture_name, config): + if config.getoption("--extend-scope"): + return "session" + return "function" + + + @pytest.fixture(scope=dynamic_scope) + def dynamic_fixture(calls=[]): + calls.append("call") + return len(calls) + + """ + ) + + testdir.makepyfile( + """ + def test_first(dynamic_fixture): + assert dynamic_fixture == 1 + + + def test_second(dynamic_fixture): + assert dynamic_fixture == 2 + + """ + ) + + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2) + + reprec = testdir.inline_run("--extend-scope") + reprec.assertoutcome(passed=1, failed=1) + + def test_dynamic_scope_bad_return(self, testdir): + testdir.makepyfile( + """ + import pytest + + def dynamic_scope(**_): + return "wrong-scope" + + @pytest.fixture(scope=dynamic_scope) + def fixture(): + pass + + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + "Fixture 'fixture' from test_dynamic_scope_bad_return.py " + "got an unexpected scope value 'wrong-scope'" + ) + def test_register_only_with_mark(self, testdir): testdir.makeconftest( """ @@ -2574,7 +2784,7 @@ class TestFixtureMarker(object): *3 passed* """ ) - assert "error" not in result.stdout.str() + result.stdout.no_fnmatch_line("*error*") def test_fixture_finalizer(self, testdir): testdir.makeconftest( @@ -2853,8 +3063,7 @@ class TestFixtureMarker(object): """ import pytest - @pytest.yield_fixture(params=[object(), object()], - ids=['alpha', 'beta']) + @pytest.fixture(params=[object(), object()], ids=['alpha', 'beta']) def fix(request): yield request.param @@ -2909,7 +3118,7 @@ class TestFixtureMarker(object): assert out1 == out2 -class TestRequestScopeAccess(object): +class TestRequestScopeAccess: pytestmark = pytest.mark.parametrize( ("scope", "ok", "error"), [ @@ -2963,7 +3172,7 @@ class TestRequestScopeAccess(object): reprec.assertoutcome(passed=1) -class TestErrors(object): +class TestErrors: def test_subfactory_missing_funcarg(self, testdir): testdir.makepyfile( """ @@ -3008,7 +3217,7 @@ class TestErrors(object): *KeyError* *ERROR*teardown*test_2* *KeyError* - *3 pass*2 error* + *3 pass*2 errors* """ ) @@ -3030,7 +3239,7 @@ class TestErrors(object): ) -class TestShowFixtures(object): +class TestShowFixtures: def test_funcarg_compat(self, testdir): config = testdir.parseconfigure("--funcargs") assert config.option.showfixtures @@ -3078,7 +3287,7 @@ class TestShowFixtures(object): *hello world* """ ) - assert "arg0" not in result.stdout.str() + result.stdout.no_fnmatch_line("*arg0*") @pytest.mark.parametrize("testmod", [True, False]) def test_show_fixtures_conftest(self, testdir, testmod): @@ -3226,9 +3435,7 @@ class TestShowFixtures(object): ) def test_show_fixtures_different_files(self, testdir): - """ - #833: --fixtures only shows fixtures from first file - """ + """`--fixtures` only shows fixtures from first file (#833).""" testdir.makepyfile( test_a=''' import pytest @@ -3315,10 +3522,10 @@ class TestShowFixtures(object): @pytest.fixture @pytest.fixture def foo(): - pass + raise NotImplementedError() -class TestContextManagerFixtureFuncs(object): +class TestContextManagerFixtureFuncs: @pytest.fixture(params=["fixture", "yield_fixture"]) def flavor(self, request, testdir, monkeypatch): monkeypatch.setenv("PYTEST_FIXTURE_FLAVOR", request.param) @@ -3339,7 +3546,6 @@ class TestContextManagerFixtureFuncs(object): def test_simple(self, testdir, flavor): testdir.makepyfile( """ - from __future__ import print_function from test_context import fixture @fixture def arg1(): @@ -3368,7 +3574,6 @@ class TestContextManagerFixtureFuncs(object): def test_scoped(self, testdir, flavor): testdir.makepyfile( """ - from __future__ import print_function from test_context import fixture @fixture(scope="module") def arg1(): @@ -3466,7 +3671,7 @@ class TestContextManagerFixtureFuncs(object): result.stdout.fnmatch_lines(["*mew*"]) -class TestParameterizedSubRequest(object): +class TestParameterizedSubRequest: def test_call_from_fixture(self, testdir): testdir.makepyfile( test_call_from_fixture=""" @@ -3591,18 +3796,34 @@ class TestParameterizedSubRequest(object): " test_foos.py::test_foo", "", "Requested fixture 'fix_with_param' defined in:", - "*fix.py:4", + "{}:4".format(fixfile), "Requested here:", "test_foos.py:4", "*1 failed*", ] ) + # With non-overlapping rootdir, passing tests_dir. + rootdir = testdir.mkdir("rootdir") + rootdir.chdir() + result = testdir.runpytest("--rootdir", rootdir, tests_dir) + result.stdout.fnmatch_lines( + [ + "The requested fixture has no parameter defined for test:", + " test_foos.py::test_foo", + "", + "Requested fixture 'fix_with_param' defined in:", + "{}:4".format(fixfile), + "Requested here:", + "{}:4".format(testfile), + "*1 failed*", + ] + ) + def test_pytest_fixture_setup_and_post_finalizer_hook(testdir): testdir.makeconftest( """ - from __future__ import print_function def pytest_fixture_setup(fixturedef, request): print('ROOT setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) def pytest_fixture_post_finalizer(fixturedef, request): @@ -3612,14 +3833,12 @@ def test_pytest_fixture_setup_and_post_finalizer_hook(testdir): testdir.makepyfile( **{ "tests/conftest.py": """ - from __future__ import print_function def pytest_fixture_setup(fixturedef, request): print('TESTS setup hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) def pytest_fixture_post_finalizer(fixturedef, request): print('TESTS finalizer hook called for {0} from {1}'.format(fixturedef.argname, request.node.name)) """, "tests/test_hooks.py": """ - from __future__ import print_function import pytest @pytest.fixture() @@ -3645,7 +3864,7 @@ def test_pytest_fixture_setup_and_post_finalizer_hook(testdir): ) -class TestScopeOrdering(object): +class TestScopeOrdering: """Class of tests that ensure fixtures are ordered based on their scopes (#2405)""" @pytest.mark.parametrize("variant", ["mark", "autouse"]) @@ -3682,7 +3901,7 @@ class TestScopeOrdering(object): request = FixtureRequest(items[0]) assert request.fixturenames == "m1 f1".split() - def test_func_closure_with_native_fixtures(self, testdir, monkeypatch): + def test_func_closure_with_native_fixtures(self, testdir, monkeypatch) -> None: """Sanity check that verifies the order returned by the closures and the actual fixture execution order: The execution order may differ because of fixture inter-dependencies. """ @@ -3732,9 +3951,8 @@ class TestScopeOrdering(object): ) testdir.runpytest() # actual fixture execution differs: dependent fixtures must be created first ("my_tmpdir") - assert ( - pytest.FIXTURE_ORDER == "s1 my_tmpdir_factory p1 m1 my_tmpdir f1 f2".split() - ) + FIXTURE_ORDER = pytest.FIXTURE_ORDER # type: ignore[attr-defined] + assert FIXTURE_ORDER == "s1 my_tmpdir_factory p1 m1 my_tmpdir f1 f2".split() def test_func_closure_module(self, testdir): testdir.makepyfile( @@ -3940,13 +4158,45 @@ class TestScopeOrdering(object): reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + def test_class_fixture_self_instance(self, testdir): + """Check that plugin classes which implement fixtures receive the plugin instance + as self (see #2270). + """ + testdir.makeconftest( + """ + import pytest + + def pytest_configure(config): + config.pluginmanager.register(MyPlugin()) + + class MyPlugin(): + def __init__(self): + self.arg = 1 + + @pytest.fixture(scope='function') + def myfix(self): + assert isinstance(self, MyPlugin) + return self.arg + """ + ) + + testdir.makepyfile( + """ + class TestClass(object): + def test_1(self, myfix): + assert myfix == 1 + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + def test_call_fixture_function_error(): """Check if an error is raised if a fixture function is called directly (#4545)""" @pytest.fixture def fix(): - return 1 + raise NotImplementedError() with pytest.raises(pytest.fail.Exception): assert fix() == 1 @@ -3993,3 +4243,140 @@ def test_fixture_param_shadowing(testdir): result.stdout.fnmatch_lines(["*::test_normal_fixture[[]a[]]*"]) result.stdout.fnmatch_lines(["*::test_normal_fixture[[]b[]]*"]) result.stdout.fnmatch_lines(["*::test_indirect[[]1[]]*"]) + + +def test_fixture_named_request(testdir): + testdir.copy_example("fixtures/test_fixture_named_request.py") + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "*'request' is a reserved word for fixtures, use another name:", + " *test_fixture_named_request.py:5", + ] + ) + + +def test_indirect_fixture_does_not_break_scope(testdir): + """Ensure that fixture scope is respected when using indirect fixtures (#570)""" + testdir.makepyfile( + """ + import pytest + instantiated = [] + + @pytest.fixture(scope="session") + def fixture_1(request): + instantiated.append(("fixture_1", request.param)) + + + @pytest.fixture(scope="session") + def fixture_2(request): + instantiated.append(("fixture_2", request.param)) + + + scenarios = [ + ("A", "a1"), + ("A", "a2"), + ("B", "b1"), + ("B", "b2"), + ("C", "c1"), + ("C", "c2"), + ] + + @pytest.mark.parametrize( + "fixture_1,fixture_2", scenarios, indirect=["fixture_1", "fixture_2"] + ) + def test_create_fixtures(fixture_1, fixture_2): + pass + + + def test_check_fixture_instantiations(): + assert instantiated == [ + ('fixture_1', 'A'), + ('fixture_2', 'a1'), + ('fixture_2', 'a2'), + ('fixture_1', 'B'), + ('fixture_2', 'b1'), + ('fixture_2', 'b2'), + ('fixture_1', 'C'), + ('fixture_2', 'c1'), + ('fixture_2', 'c2'), + ] + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=7) + + +def test_fixture_parametrization_nparray(testdir): + pytest.importorskip("numpy") + + testdir.makepyfile( + """ + from numpy import linspace + from pytest import fixture + + @fixture(params=linspace(1, 10, 10)) + def value(request): + return request.param + + def test_bug(value): + assert value == value + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=10) + + +def test_fixture_arg_ordering(testdir): + """ + This test describes how fixtures in the same scope but without explicit dependencies + between them are created. While users should make dependencies explicit, often + they rely on this order, so this test exists to catch regressions in this regard. + See #6540 and #6492. + """ + p1 = testdir.makepyfile( + """ + import pytest + + suffixes = [] + + @pytest.fixture + def fix_1(): suffixes.append("fix_1") + @pytest.fixture + def fix_2(): suffixes.append("fix_2") + @pytest.fixture + def fix_3(): suffixes.append("fix_3") + @pytest.fixture + def fix_4(): suffixes.append("fix_4") + @pytest.fixture + def fix_5(): suffixes.append("fix_5") + + @pytest.fixture + def fix_combined(fix_1, fix_2, fix_3, fix_4, fix_5): pass + + def test_suffix(fix_combined): + assert suffixes == ["fix_1", "fix_2", "fix_3", "fix_4", "fix_5"] + """ + ) + result = testdir.runpytest("-vv", str(p1)) + assert result.ret == 0 + + +def test_yield_fixture_with_no_value(testdir): + testdir.makepyfile( + """ + import pytest + @pytest.fixture(name='custom') + def empty_yield(): + if False: + yield + + def test_fixt(custom): + pass + """ + ) + expected = "E ValueError: custom did not yield a value" + result = testdir.runpytest() + result.assert_outcomes(errors=1) + result.stdout.fnmatch_lines([expected]) + assert result.ret == ExitCode.TESTS_FAILED diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/integration.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/integration.py index d2fea2dc5ca..854593a65c0 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/integration.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/integration.py @@ -1,17 +1,20 @@ -# -*- coding: utf-8 -*- +from typing import Any + import pytest -from _pytest import python from _pytest import runner +from _pytest._code import getfslineno -class TestOEJSKITSpecials(object): - def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage +class TestOEJSKITSpecials: + def test_funcarg_non_pycollectobj( + self, testdir, recwarn + ) -> None: # rough jstests usage testdir.makeconftest( """ import pytest def pytest_pycollect_makeitem(collector, name, obj): if name == "MyClass": - return MyCollector(name, parent=collector) + return MyCollector.from_parent(collector, name=name) class MyCollector(pytest.Collector): def reportinfo(self): return self.fspath, 3, "xyz" @@ -29,19 +32,20 @@ class TestOEJSKITSpecials(object): ) # this hook finds funcarg factories rep = runner.collect_one_node(collector=modcol) - clscol = rep.result[0] + # TODO: Don't treat as Any. + clscol = rep.result[0] # type: Any clscol.obj = lambda arg1: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert clscol.funcargs["arg1"] == 42 - def test_autouse_fixture(self, testdir): # rough jstests usage + def test_autouse_fixture(self, testdir, recwarn) -> None: # rough jstests usage testdir.makeconftest( """ import pytest def pytest_pycollect_makeitem(collector, name, obj): if name == "MyClass": - return MyCollector(name, parent=collector) + return MyCollector.from_parent(collector, name=name) class MyCollector(pytest.Collector): def reportinfo(self): return self.fspath, 3, "xyz" @@ -62,40 +66,41 @@ class TestOEJSKITSpecials(object): ) # this hook finds funcarg factories rep = runner.collect_one_node(modcol) - clscol = rep.result[0] + # TODO: Don't treat as Any. + clscol = rep.result[0] # type: Any clscol.obj = lambda: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert not clscol.funcargs -def test_wrapped_getfslineno(): +def test_wrapped_getfslineno() -> None: def func(): pass def wrap(f): - func.__wrapped__ = f - func.patchings = ["qwe"] + func.__wrapped__ = f # type: ignore + func.patchings = ["qwe"] # type: ignore return func @wrap def wrapped_func(x, y, z): pass - fs, lineno = python.getfslineno(wrapped_func) - fs2, lineno2 = python.getfslineno(wrap) + fs, lineno = getfslineno(wrapped_func) + fs2, lineno2 = getfslineno(wrap) assert lineno > lineno2, "getfslineno does not unwrap correctly" -class TestMockDecoration(object): - def test_wrapped_getfuncargnames(self): +class TestMockDecoration: + def test_wrapped_getfuncargnames(self) -> None: from _pytest.compat import getfuncargnames def wrap(f): def func(): pass - func.__wrapped__ = f + func.__wrapped__ = f # type: ignore return func @wrap @@ -105,21 +110,15 @@ class TestMockDecoration(object): values = getfuncargnames(f) assert values == ("x",) - @pytest.mark.xfail( - strict=False, reason="getfuncargnames breaks if mock is imported" - ) - def test_wrapped_getfuncargnames_patching(self): + def test_getfuncargnames_patching(self): from _pytest.compat import getfuncargnames + from unittest.mock import patch - def wrap(f): - def func(): + class T: + def original(self, x, y, z): pass - func.__wrapped__ = f - func.patchings = ["qwe"] - return func - - @wrap + @patch.object(T, "original") def f(x, y, z): pass @@ -127,7 +126,6 @@ class TestMockDecoration(object): assert values == ("y", "z") def test_unittest_mock(self, testdir): - pytest.importorskip("unittest.mock") testdir.makepyfile( """ import unittest.mock @@ -143,7 +141,6 @@ class TestMockDecoration(object): reprec.assertoutcome(passed=1) def test_unittest_mock_and_fixture(self, testdir): - pytest.importorskip("unittest.mock") testdir.makepyfile( """ import os.path @@ -165,7 +162,6 @@ class TestMockDecoration(object): reprec.assertoutcome(passed=1) def test_unittest_mock_and_pypi_mock(self, testdir): - pytest.importorskip("unittest.mock") pytest.importorskip("mock", "1.0.1") testdir.makepyfile( """ @@ -188,6 +184,34 @@ class TestMockDecoration(object): reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + def test_mock_sentinel_check_against_numpy_like(self, testdir): + """Ensure our function that detects mock arguments compares against sentinels using + identity to circumvent objects which can't be compared with equality against others + in a truth context, like with numpy arrays (#5606). + """ + testdir.makepyfile( + dummy=""" + class NumpyLike: + def __init__(self, value): + self.value = value + def __eq__(self, other): + raise ValueError("like numpy, cannot compare against others for truth") + FOO = NumpyLike(10) + """ + ) + testdir.makepyfile( + """ + from unittest.mock import patch + import dummy + class Test(object): + @patch("dummy.FOO", new=dummy.NumpyLike(50)) + def test_hello(self): + assert dummy.FOO.value == 50 + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + def test_mock(self, testdir): pytest.importorskip("mock", "1.0.1") testdir.makepyfile( @@ -264,7 +288,7 @@ class TestMockDecoration(object): reprec.assertoutcome(passed=1) -class TestReRunTests(object): +class TestReRunTests: def test_rerun(self, testdir): testdir.makeconftest( """ @@ -304,13 +328,14 @@ class TestReRunTests(object): ) -def test_pytestconfig_is_session_scoped(): +def test_pytestconfig_is_session_scoped() -> None: from _pytest.fixtures import pytestconfig - assert pytestconfig._pytestfixturefunction.scope == "session" + marker = pytestconfig._pytestfixturefunction # type: ignore + assert marker.scope == "session" -class TestNoselikeTestAttribute(object): +class TestNoselikeTestAttribute: def test_module_with_global_test(self, testdir): testdir.makepyfile( """ @@ -394,7 +419,7 @@ class TestNoselikeTestAttribute(object): assert not call.items -class TestParameterize(object): +class TestParameterize: """#351""" def test_idfn_marker(self, testdir): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py index d90787894ae..6b59104567a 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/metafunc.py @@ -1,42 +1,55 @@ -# -*- coding: utf-8 -*- +import itertools import re import sys import textwrap +from typing import Any +from typing import cast +from typing import Dict +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import Union import attr import hypothesis -import six from hypothesis import strategies import pytest from _pytest import fixtures from _pytest import python -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG - -PY3 = sys.version_info >= (3, 0) - - -class TestMetafunc(object): - def Metafunc(self, func, config=None): - # the unit tests of this class check if things work correctly +from _pytest.compat import _format_args +from _pytest.compat import getfuncargnames +from _pytest.compat import NOTSET +from _pytest.outcomes import fail +from _pytest.pytester import Testdir +from _pytest.python import _idval +from _pytest.python import idmaker + + +class TestMetafunc: + def Metafunc(self, func, config=None) -> python.Metafunc: + # The unit tests of this class check if things work correctly # on the funcarg level, so we don't need a full blown - # initiliazation - class FixtureInfo(object): + # initialization. + class FuncFixtureInfoMock: name2fixturedefs = None def __init__(self, names): self.names_closure = names @attr.s - class DefinitionMock(object): + class DefinitionMock(python.FunctionDefinition): obj = attr.ib() + _nodeid = attr.ib() - names = fixtures.getfuncargnames(func) - fixtureinfo = FixtureInfo(names) - definition = DefinitionMock(func) + names = getfuncargnames(func) + fixtureinfo = FuncFixtureInfoMock(names) # type: Any + definition = DefinitionMock._create(func, "mock::nodeid") # type: Any return python.Metafunc(definition, fixtureinfo, config) - def test_no_funcargs(self, testdir): + def test_no_funcargs(self) -> None: def function(): pass @@ -44,7 +57,7 @@ class TestMetafunc(object): assert not metafunc.fixturenames repr(metafunc._calls) - def test_function_basic(self): + def test_function_basic(self) -> None: def func(arg1, arg2="qwe"): pass @@ -54,7 +67,7 @@ class TestMetafunc(object): assert metafunc.function is func assert metafunc.cls is None - def test_parametrize_error(self): + def test_parametrize_error(self) -> None: def func(x, y): pass @@ -66,31 +79,80 @@ class TestMetafunc(object): pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6])) pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6])) - def test_parametrize_bad_scope(self, testdir): + with pytest.raises(TypeError, match="^ids must be a callable or an iterable$"): + metafunc.parametrize("y", [5, 6], ids=42) # type: ignore[arg-type] + + def test_parametrize_error_iterator(self) -> None: + def func(x): + raise NotImplementedError() + + class Exc(Exception): + def __repr__(self): + return "Exc(from_gen)" + + def gen() -> Iterator[Union[int, None, Exc]]: + yield 0 + yield None + yield Exc() + + metafunc = self.Metafunc(func) + # When the input is an iterator, only len(args) are taken, + # so the bad Exc isn't reached. + metafunc.parametrize("x", [1, 2], ids=gen()) # type: ignore[arg-type] + assert [(x.funcargs, x.id) for x in metafunc._calls] == [ + ({"x": 1}, "0"), + ({"x": 2}, "2"), + ] + with pytest.raises( + fail.Exception, + match=( + r"In func: ids must be list of string/float/int/bool, found:" + r" Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2" + ), + ): + metafunc.parametrize("x", [1, 2, 3], ids=gen()) # type: ignore[arg-type] + + def test_parametrize_bad_scope(self) -> None: def func(x): pass metafunc = self.Metafunc(func) with pytest.raises( - pytest.fail.Exception, + fail.Exception, match=r"parametrize\(\) call in func got an unexpected scope value 'doggy'", ): - metafunc.parametrize("x", [1], scope="doggy") + metafunc.parametrize("x", [1], scope="doggy") # type: ignore[arg-type] - def test_find_parametrized_scope(self): - """unittest for _find_parametrized_scope (#3941)""" + def test_parametrize_request_name(self, testdir: Testdir) -> None: + """Show proper error when 'request' is used as a parameter name in parametrize (#6183)""" + + def func(request): + raise NotImplementedError() + + metafunc = self.Metafunc(func) + with pytest.raises( + fail.Exception, + match=r"'request' is a reserved name and cannot be used in @pytest.mark.parametrize", + ): + metafunc.parametrize("request", [1]) + + def test_find_parametrized_scope(self) -> None: + """Unit test for _find_parametrized_scope (#3941).""" from _pytest.python import _find_parametrized_scope @attr.s - class DummyFixtureDef(object): + class DummyFixtureDef: scope = attr.ib() - fixtures_defs = dict( - session_fix=[DummyFixtureDef("session")], - package_fix=[DummyFixtureDef("package")], - module_fix=[DummyFixtureDef("module")], - class_fix=[DummyFixtureDef("class")], - func_fix=[DummyFixtureDef("function")], + fixtures_defs = cast( + Dict[str, Sequence[fixtures.FixtureDef[object]]], + dict( + session_fix=[DummyFixtureDef("session")], + package_fix=[DummyFixtureDef("package")], + module_fix=[DummyFixtureDef("module")], + class_fix=[DummyFixtureDef("class")], + func_fix=[DummyFixtureDef("function")], + ), ) # use arguments to determine narrow scope; the cause of the bug is that it would look on all @@ -123,7 +185,7 @@ class TestMetafunc(object): == "module" ) - def test_parametrize_and_id(self): + def test_parametrize_and_id(self) -> None: def func(x, y): pass @@ -134,38 +196,56 @@ class TestMetafunc(object): ids = [x.id for x in metafunc._calls] assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"] - def test_parametrize_and_id_unicode(self): + def test_parametrize_and_id_unicode(self) -> None: """Allow unicode strings for "ids" parameter in Python 2 (##1905)""" def func(x): pass metafunc = self.Metafunc(func) - metafunc.parametrize("x", [1, 2], ids=[u"basic", u"advanced"]) + metafunc.parametrize("x", [1, 2], ids=["basic", "advanced"]) ids = [x.id for x in metafunc._calls] - assert ids == [u"basic", u"advanced"] + assert ids == ["basic", "advanced"] - def test_parametrize_with_wrong_number_of_ids(self, testdir): + def test_parametrize_with_wrong_number_of_ids(self) -> None: def func(x, y): pass metafunc = self.Metafunc(func) - with pytest.raises(pytest.fail.Exception): + with pytest.raises(fail.Exception): metafunc.parametrize("x", [1, 2], ids=["basic"]) - with pytest.raises(pytest.fail.Exception): + with pytest.raises(fail.Exception): metafunc.parametrize( ("x", "y"), [("abc", "def"), ("ghi", "jkl")], ids=["one"] ) - def test_parametrize_empty_list(self): + def test_parametrize_ids_iterator_without_mark(self) -> None: + def func(x, y): + pass + + it = itertools.count() + + metafunc = self.Metafunc(func) + metafunc.parametrize("x", [1, 2], ids=it) + metafunc.parametrize("y", [3, 4], ids=it) + ids = [x.id for x in metafunc._calls] + assert ids == ["0-2", "0-3", "1-2", "1-3"] + + metafunc = self.Metafunc(func) + metafunc.parametrize("x", [1, 2], ids=it) + metafunc.parametrize("y", [3, 4], ids=it) + ids = [x.id for x in metafunc._calls] + assert ids == ["4-6", "4-7", "5-6", "5-7"] + + def test_parametrize_empty_list(self) -> None: """#510""" def func(y): pass - class MockConfig(object): + class MockConfig: def getini(self, name): return "" @@ -180,13 +260,13 @@ class TestMetafunc(object): metafunc.parametrize("y", []) assert "skip" == metafunc._calls[0].marks[0].name - def test_parametrize_with_userobjects(self): + def test_parametrize_with_userobjects(self) -> None: def func(x, y): pass metafunc = self.Metafunc(func) - class A(object): + class A: pass metafunc.parametrize("x", [A(), A()]) @@ -200,43 +280,37 @@ class TestMetafunc(object): @hypothesis.settings( deadline=400.0 ) # very close to std deadline and CI boxes are not reliable in CPU power - def test_idval_hypothesis(self, value): - from _pytest.python import _idval - - escaped = _idval(value, "a", 6, None, item=None, config=None) - assert isinstance(escaped, six.text_type) + def test_idval_hypothesis(self, value) -> None: + escaped = _idval(value, "a", 6, None, nodeid=None, config=None) + assert isinstance(escaped, str) escaped.encode("ascii") - def test_unicode_idval(self): - """This tests that Unicode strings outside the ASCII character set get + def test_unicode_idval(self) -> None: + """Test that Unicode strings outside the ASCII character set get escaped, using byte escapes if they're in that range or unicode escapes if they're not. """ - from _pytest.python import _idval - values = [ - (u"", ""), - (u"ascii", "ascii"), - (u"ação", "a\\xe7\\xe3o"), - (u"josé@blah.com", "jos\\xe9@blah.com"), + ("", r""), + ("ascii", r"ascii"), + ("ação", r"a\xe7\xe3o"), + ("josé@blah.com", r"jos\xe9@blah.com"), ( - u"δοκ.ιμή@παράδειγμα.δοκιμή", - "\\u03b4\\u03bf\\u03ba.\\u03b9\\u03bc\\u03ae@\\u03c0\\u03b1\\u03c1\\u03ac\\u03b4\\u03b5\\u03b9\\u03b3" - "\\u03bc\\u03b1.\\u03b4\\u03bf\\u03ba\\u03b9\\u03bc\\u03ae", + r"δοκ.ιμή@παράδειγμα.δοκιμή", + r"\u03b4\u03bf\u03ba.\u03b9\u03bc\u03ae@\u03c0\u03b1\u03c1\u03ac\u03b4\u03b5\u03b9\u03b3" + r"\u03bc\u03b1.\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae", ), ] for val, expected in values: - assert _idval(val, "a", 6, None, item=None, config=None) == expected + assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected - def test_unicode_idval_with_config(self): - """unittest for expected behavior to obtain ids with + def test_unicode_idval_with_config(self) -> None: + """Unit test for expected behavior to obtain ids with disable_test_id_escaping_and_forfeit_all_rights_to_community_support - option. (#5294) - """ - from _pytest.python import _idval + option (#5294).""" - class MockConfig(object): + class MockConfig: def __init__(self, config): self.config = config @@ -253,37 +327,30 @@ class TestMetafunc(object): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" values = [ - (u"ação", MockConfig({option: True}), u"ação"), - (u"ação", MockConfig({option: False}), "a\\xe7\\xe3o"), - ] + ("ação", MockConfig({option: True}), "ação"), + ("ação", MockConfig({option: False}), "a\\xe7\\xe3o"), + ] # type: List[Tuple[str, Any, str]] for val, config, expected in values: - assert _idval(val, "a", 6, None, item=None, config=config) == expected - - def test_bytes_idval(self): - """unittest for the expected behavior to obtain ids for parametrized - bytes values: - - python2: non-ascii strings are considered bytes and formatted using - "binary escape", where any byte < 127 is escaped into its hex form. - - python3: bytes objects are always escaped using "binary escape". - """ - from _pytest.python import _idval + actual = _idval(val, "a", 6, None, nodeid=None, config=config) + assert actual == expected + def test_bytes_idval(self) -> None: + """Unit test for the expected behavior to obtain ids for parametrized + bytes values: bytes objects are always escaped using "binary escape".""" values = [ - (b"", ""), - (b"\xc3\xb4\xff\xe4", "\\xc3\\xb4\\xff\\xe4"), - (b"ascii", "ascii"), - (u"αρά".encode("utf-8"), "\\xce\\xb1\\xcf\\x81\\xce\\xac"), + (b"", r""), + (b"\xc3\xb4\xff\xe4", r"\xc3\xb4\xff\xe4"), + (b"ascii", r"ascii"), + ("αρά".encode(), r"\xce\xb1\xcf\x81\xce\xac"), ] for val, expected in values: - assert _idval(val, "a", 6, idfn=None, item=None, config=None) == expected + assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected - def test_class_or_function_idval(self): - """unittest for the expected behavior to obtain ids for parametrized - values that are classes or functions: their __name__. - """ - from _pytest.python import _idval + def test_class_or_function_idval(self) -> None: + """Unit test for the expected behavior to obtain ids for parametrized + values that are classes or functions: their __name__.""" - class TestClass(object): + class TestClass: pass def test_function(): @@ -291,12 +358,18 @@ class TestMetafunc(object): values = [(TestClass, "TestClass"), (test_function, "test_function")] for val, expected in values: - assert _idval(val, "a", 6, None, item=None, config=None) == expected + assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected - def test_idmaker_autoname(self): - """#250""" - from _pytest.python import idmaker + def test_notset_idval(self) -> None: + """Test that a NOTSET value (used by an empty parameterset) generates + a proper ID. + Regression test for #7686. + """ + assert _idval(NOTSET, "a", 0, None, nodeid=None, config=None) == "a0" + + def test_idmaker_autoname(self) -> None: + """#250""" result = idmaker( ("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)] ) @@ -307,18 +380,14 @@ class TestMetafunc(object): ) assert result == ["a0-1.0", "a1-b1"] # unicode mixing, issue250 - result = idmaker((u"a", "b"), [pytest.param({}, b"\xc3\xb4")]) + result = idmaker(("a", "b"), [pytest.param({}, b"\xc3\xb4")]) assert result == ["a0-\\xc3\\xb4"] - def test_idmaker_with_bytes_regex(self): - from _pytest.python import idmaker - + def test_idmaker_with_bytes_regex(self) -> None: result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)]) assert result == ["foo"] - def test_idmaker_native_strings(self): - from _pytest.python import idmaker - + def test_idmaker_native_strings(self) -> None: result = idmaker( ("a", "b"), [ @@ -333,7 +402,7 @@ class TestMetafunc(object): pytest.param({7}, set("seven")), pytest.param(tuple("eight"), (8, -8, 8)), pytest.param(b"\xc3\xb4", b"name"), - pytest.param(b"\xc3\xb4", u"other"), + pytest.param(b"\xc3\xb4", "other"), ], ) assert result == [ @@ -351,9 +420,7 @@ class TestMetafunc(object): "\\xc3\\xb4-other", ] - def test_idmaker_non_printable_characters(self): - from _pytest.python import idmaker - + def test_idmaker_non_printable_characters(self) -> None: result = idmaker( ("s", "n"), [ @@ -367,9 +434,7 @@ class TestMetafunc(object): ) assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"] - def test_idmaker_manual_ids_must_be_printable(self): - from _pytest.python import idmaker - + def test_idmaker_manual_ids_must_be_printable(self) -> None: result = idmaker( ("s",), [ @@ -379,21 +444,19 @@ class TestMetafunc(object): ) assert result == ["hello \\x00", "hello \\x05"] - def test_idmaker_enum(self): - from _pytest.python import idmaker - + def test_idmaker_enum(self) -> None: enum = pytest.importorskip("enum") e = enum.Enum("Foo", "one, two") result = idmaker(("a", "b"), [pytest.param(e.one, e.two)]) assert result == ["Foo.one-Foo.two"] - def test_idmaker_idfn(self): + def test_idmaker_idfn(self) -> None: """#351""" - from _pytest.python import idmaker - def ids(val): + def ids(val: object) -> Optional[str]: if isinstance(val, Exception): return repr(val) + return None result = idmaker( ("a", "b"), @@ -406,11 +469,10 @@ class TestMetafunc(object): ) assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"] - def test_idmaker_idfn_unique_names(self): + def test_idmaker_idfn_unique_names(self) -> None: """#351""" - from _pytest.python import idmaker - def ids(val): + def ids(val: object) -> str: return "a" result = idmaker( @@ -424,14 +486,13 @@ class TestMetafunc(object): ) assert result == ["a-a0", "a-a1", "a-a2"] - def test_idmaker_with_idfn_and_config(self): - """unittest for expected behavior to create ids with idfn and + def test_idmaker_with_idfn_and_config(self) -> None: + """Unit test for expected behavior to create ids with idfn and disable_test_id_escaping_and_forfeit_all_rights_to_community_support - option. (#5294) + option (#5294). """ - from _pytest.python import idmaker - class MockConfig(object): + class MockConfig: def __init__(self, config): self.config = config @@ -448,23 +509,22 @@ class TestMetafunc(object): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" values = [ - (MockConfig({option: True}), u"ação"), + (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), - ] + ] # type: List[Tuple[Any, str]] for config, expected in values: result = idmaker( - ("a",), [pytest.param("string")], idfn=lambda _: u"ação", config=config + ("a",), [pytest.param("string")], idfn=lambda _: "ação", config=config, ) assert result == [expected] - def test_idmaker_with_ids_and_config(self): - """unittest for expected behavior to create ids with ids and + def test_idmaker_with_ids_and_config(self) -> None: + """Unit test for expected behavior to create ids with ids and disable_test_id_escaping_and_forfeit_all_rights_to_community_support - option. (#5294) + option (#5294). """ - from _pytest.python import idmaker - class MockConfig(object): + class MockConfig: def __init__(self, config): self.config = config @@ -481,16 +541,16 @@ class TestMetafunc(object): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" values = [ - (MockConfig({option: True}), u"ação"), + (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), - ] + ] # type: List[Tuple[Any, str]] for config, expected in values: result = idmaker( - ("a",), [pytest.param("string")], ids=[u"ação"], config=config + ("a",), [pytest.param("string")], ids=["ação"], config=config, ) assert result == [expected] - def test_parametrize_ids_exception(self, testdir): + def test_parametrize_ids_exception(self, testdir: Testdir) -> None: """ :param testdir: the instance of Testdir class, a temporary test directory. @@ -510,12 +570,12 @@ class TestMetafunc(object): result = testdir.runpytest() result.stdout.fnmatch_lines( [ - "*test_foo: error raised while trying to determine id of parameter 'arg' at position 0", "*Exception: bad ids", + "*test_foo: error raised while trying to determine id of parameter 'arg' at position 0", ] ) - def test_parametrize_ids_returns_non_string(self, testdir): + def test_parametrize_ids_returns_non_string(self, testdir: Testdir) -> None: testdir.makepyfile( """\ import pytest @@ -526,21 +586,30 @@ class TestMetafunc(object): @pytest.mark.parametrize("arg", ({1: 2}, {3, 4}), ids=ids) def test(arg): assert arg + + @pytest.mark.parametrize("arg", (1, 2.0, True), ids=ids) + def test_int(arg): + assert arg """ ) - assert testdir.runpytest().ret == 0 - - def test_idmaker_with_ids(self): - from _pytest.python import idmaker + result = testdir.runpytest("-vv", "-s") + result.stdout.fnmatch_lines( + [ + "test_parametrize_ids_returns_non_string.py::test[arg0] PASSED", + "test_parametrize_ids_returns_non_string.py::test[arg1] PASSED", + "test_parametrize_ids_returns_non_string.py::test_int[1] PASSED", + "test_parametrize_ids_returns_non_string.py::test_int[2.0] PASSED", + "test_parametrize_ids_returns_non_string.py::test_int[True] PASSED", + ] + ) + def test_idmaker_with_ids(self) -> None: result = idmaker( ("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None] ) assert result == ["a", "3-4"] - def test_idmaker_with_paramset_id(self): - from _pytest.python import idmaker - + def test_idmaker_with_paramset_id(self) -> None: result = idmaker( ("a", "b"), [pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")], @@ -548,15 +617,13 @@ class TestMetafunc(object): ) assert result == ["me", "you"] - def test_idmaker_with_ids_unique_names(self): - from _pytest.python import idmaker - + def test_idmaker_with_ids_unique_names(self) -> None: result = idmaker( ("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"] ) assert result == ["a0", "a1", "b0", "c", "b1"] - def test_parametrize_indirect(self): + def test_parametrize_indirect(self) -> None: """#714""" def func(x, y): @@ -571,7 +638,7 @@ class TestMetafunc(object): assert metafunc._calls[0].params == dict(x=1, y=2) assert metafunc._calls[1].params == dict(x=1, y=3) - def test_parametrize_indirect_list(self): + def test_parametrize_indirect_list(self) -> None: """#714""" def func(x, y): @@ -582,7 +649,7 @@ class TestMetafunc(object): assert metafunc._calls[0].funcargs == dict(y="b") assert metafunc._calls[0].params == dict(x="a") - def test_parametrize_indirect_list_all(self): + def test_parametrize_indirect_list_all(self) -> None: """#714""" def func(x, y): @@ -593,7 +660,7 @@ class TestMetafunc(object): assert metafunc._calls[0].funcargs == {} assert metafunc._calls[0].params == dict(x="a", y="b") - def test_parametrize_indirect_list_empty(self): + def test_parametrize_indirect_list_empty(self) -> None: """#714""" def func(x, y): @@ -604,7 +671,18 @@ class TestMetafunc(object): assert metafunc._calls[0].funcargs == dict(x="a", y="b") assert metafunc._calls[0].params == {} - def test_parametrize_indirect_list_functional(self, testdir): + def test_parametrize_indirect_wrong_type(self) -> None: + def func(x, y): + pass + + metafunc = self.Metafunc(func) + with pytest.raises( + fail.Exception, + match="In func: expected Sequence or boolean for indirect, got dict", + ): + metafunc.parametrize("x, y", [("a", "b")], indirect={}) # type: ignore[arg-type] + + def test_parametrize_indirect_list_functional(self, testdir: Testdir) -> None: """ #714 Test parametrization with 'indirect' parameter applied on @@ -633,17 +711,19 @@ class TestMetafunc(object): result = testdir.runpytest("-v") result.stdout.fnmatch_lines(["*test_simple*a-b*", "*1 passed*"]) - def test_parametrize_indirect_list_error(self, testdir): + def test_parametrize_indirect_list_error(self) -> None: """#714""" def func(x, y): pass metafunc = self.Metafunc(func) - with pytest.raises(pytest.fail.Exception): + with pytest.raises(fail.Exception): metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "z"]) - def test_parametrize_uses_no_fixture_error_indirect_false(self, testdir): + def test_parametrize_uses_no_fixture_error_indirect_false( + self, testdir: Testdir + ) -> None: """The 'uses no fixture' error tells the user at collection time that the parametrize data they've set up doesn't correspond to the fixtures in their test function, rather than silently ignoring this @@ -663,7 +743,9 @@ class TestMetafunc(object): result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no argument 'y'*"]) - def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): + def test_parametrize_uses_no_fixture_error_indirect_true( + self, testdir: Testdir + ) -> None: """#714""" testdir.makepyfile( """ @@ -683,7 +765,9 @@ class TestMetafunc(object): result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) - def test_parametrize_indirect_uses_no_fixture_error_indirect_string(self, testdir): + def test_parametrize_indirect_uses_no_fixture_error_indirect_string( + self, testdir: Testdir + ) -> None: """#714""" testdir.makepyfile( """ @@ -700,7 +784,9 @@ class TestMetafunc(object): result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) - def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): + def test_parametrize_indirect_uses_no_fixture_error_indirect_list( + self, testdir: Testdir + ) -> None: """#714""" testdir.makepyfile( """ @@ -717,7 +803,7 @@ class TestMetafunc(object): result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) - def test_parametrize_argument_not_in_indirect_list(self, testdir): + def test_parametrize_argument_not_in_indirect_list(self, testdir: Testdir) -> None: """#714""" testdir.makepyfile( """ @@ -736,7 +822,7 @@ class TestMetafunc(object): def test_parametrize_gives_indicative_error_on_function_with_default_argument( self, testdir - ): + ) -> None: testdir.makepyfile( """ import pytest @@ -751,7 +837,7 @@ class TestMetafunc(object): ["*already takes an argument 'y' with a default value"] ) - def test_parametrize_functional(self, testdir): + def test_parametrize_functional(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -772,7 +858,7 @@ class TestMetafunc(object): ["*test_simple*1-2*", "*test_simple*2-2*", "*2 passed*"] ) - def test_parametrize_onearg(self): + def test_parametrize_onearg(self) -> None: metafunc = self.Metafunc(lambda x: None) metafunc.parametrize("x", [1, 2]) assert len(metafunc._calls) == 2 @@ -781,7 +867,7 @@ class TestMetafunc(object): assert metafunc._calls[1].funcargs == dict(x=2) assert metafunc._calls[1].id == "2" - def test_parametrize_onearg_indirect(self): + def test_parametrize_onearg_indirect(self) -> None: metafunc = self.Metafunc(lambda x: None) metafunc.parametrize("x", [1, 2], indirect=True) assert metafunc._calls[0].params == dict(x=1) @@ -789,7 +875,7 @@ class TestMetafunc(object): assert metafunc._calls[1].params == dict(x=2) assert metafunc._calls[1].id == "2" - def test_parametrize_twoargs(self): + def test_parametrize_twoargs(self) -> None: metafunc = self.Metafunc(lambda x, y: None) metafunc.parametrize(("x", "y"), [(1, 2), (3, 4)]) assert len(metafunc._calls) == 2 @@ -798,7 +884,7 @@ class TestMetafunc(object): assert metafunc._calls[1].funcargs == dict(x=3, y=4) assert metafunc._calls[1].id == "3-4" - def test_parametrize_multiple_times(self, testdir): + def test_parametrize_multiple_times(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -815,7 +901,7 @@ class TestMetafunc(object): assert result.ret == 1 result.assert_outcomes(failed=6) - def test_parametrize_CSV(self, testdir): + def test_parametrize_CSV(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -827,7 +913,7 @@ class TestMetafunc(object): reprec = testdir.inline_run() reprec.assertoutcome(passed=2) - def test_parametrize_class_scenarios(self, testdir): + def test_parametrize_class_scenarios(self, testdir: Testdir) -> None: testdir.makepyfile( """ # same as doc/en/example/parametrize scenario example @@ -869,34 +955,34 @@ class TestMetafunc(object): """ ) - def test_format_args(self): + def test_format_args(self) -> None: def function1(): pass - assert fixtures._format_args(function1) == "()" + assert _format_args(function1) == "()" def function2(arg1): pass - assert fixtures._format_args(function2) == "(arg1)" + assert _format_args(function2) == "(arg1)" def function3(arg1, arg2="qwe"): pass - assert fixtures._format_args(function3) == "(arg1, arg2='qwe')" + assert _format_args(function3) == "(arg1, arg2='qwe')" def function4(arg1, *args, **kwargs): pass - assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)" + assert _format_args(function4) == "(arg1, *args, **kwargs)" -class TestMetafuncFunctional(object): - def test_attributes(self, testdir): +class TestMetafuncFunctional: + def test_attributes(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ # assumes that generate/provide runs in the same process - import sys, pytest, six + import sys, pytest def pytest_generate_tests(metafunc): metafunc.parametrize('metafunc', [metafunc]) @@ -914,15 +1000,15 @@ class TestMetafuncFunctional(object): def test_method(self, metafunc, pytestconfig): assert metafunc.config == pytestconfig assert metafunc.module.__name__ == __name__ - unbound = six.get_unbound_function(TestClass.test_method) + unbound = TestClass.test_method assert metafunc.function == unbound assert metafunc.cls == TestClass """ ) - result = testdir.runpytest(p, "-v", SHOW_PYTEST_WARNINGS_ARG) + result = testdir.runpytest(p, "-v") result.assert_outcomes(passed=2) - def test_two_functions(self, testdir): + def test_two_functions(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ def pytest_generate_tests(metafunc): @@ -935,7 +1021,7 @@ class TestMetafuncFunctional(object): assert arg1 in (10, 20) """ ) - result = testdir.runpytest("-v", p, SHOW_PYTEST_WARNINGS_ARG) + result = testdir.runpytest("-v", p) result.stdout.fnmatch_lines( [ "*test_func1*0*PASS*", @@ -946,7 +1032,7 @@ class TestMetafuncFunctional(object): ] ) - def test_noself_in_method(self, testdir): + def test_noself_in_method(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ def pytest_generate_tests(metafunc): @@ -960,7 +1046,7 @@ class TestMetafuncFunctional(object): result = testdir.runpytest(p) result.assert_outcomes(passed=1) - def test_generate_tests_in_class(self, testdir): + def test_generate_tests_in_class(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ class TestClass(object): @@ -971,10 +1057,10 @@ class TestMetafuncFunctional(object): assert hello == "world" """ ) - result = testdir.runpytest("-v", p, SHOW_PYTEST_WARNINGS_ARG) + result = testdir.runpytest("-v", p) result.stdout.fnmatch_lines(["*test_myfunc*hello*PASS*", "*1 passed*"]) - def test_two_functions_not_same_instance(self, testdir): + def test_two_functions_not_same_instance(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ def pytest_generate_tests(metafunc): @@ -986,12 +1072,12 @@ class TestMetafuncFunctional(object): self.x = 1 """ ) - result = testdir.runpytest("-v", p, SHOW_PYTEST_WARNINGS_ARG) + result = testdir.runpytest("-v", p) result.stdout.fnmatch_lines( ["*test_func*0*PASS*", "*test_func*1*PASS*", "*2 pass*"] ) - def test_issue28_setup_method_in_generate_tests(self, testdir): + def test_issue28_setup_method_in_generate_tests(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ def pytest_generate_tests(metafunc): @@ -1004,10 +1090,10 @@ class TestMetafuncFunctional(object): self.val = 1 """ ) - result = testdir.runpytest(p, SHOW_PYTEST_WARNINGS_ARG) + result = testdir.runpytest(p) result.assert_outcomes(passed=1) - def test_parametrize_functional2(self, testdir): + def test_parametrize_functional2(self, testdir: Testdir) -> None: testdir.makepyfile( """ def pytest_generate_tests(metafunc): @@ -1022,7 +1108,7 @@ class TestMetafuncFunctional(object): ["*(1, 4)*", "*(1, 5)*", "*(2, 4)*", "*(2, 5)*", "*4 failed*"] ) - def test_parametrize_and_inner_getfixturevalue(self, testdir): + def test_parametrize_and_inner_getfixturevalue(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ def pytest_generate_tests(metafunc): @@ -1046,7 +1132,7 @@ class TestMetafuncFunctional(object): result = testdir.runpytest("-v", p) result.stdout.fnmatch_lines(["*test_func1*1*PASS*", "*1 passed*"]) - def test_parametrize_on_setup_arg(self, testdir): + def test_parametrize_on_setup_arg(self, testdir: Testdir) -> None: p = testdir.makepyfile( """ def pytest_generate_tests(metafunc): @@ -1069,7 +1155,7 @@ class TestMetafuncFunctional(object): result = testdir.runpytest("-v", p) result.stdout.fnmatch_lines(["*test_func*1*PASS*", "*1 passed*"]) - def test_parametrize_with_ids(self, testdir): + def test_parametrize_with_ids(self, testdir: Testdir) -> None: testdir.makeini( """ [pytest] @@ -1093,7 +1179,7 @@ class TestMetafuncFunctional(object): ["*test_function*basic*PASSED", "*test_function*advanced*FAILED"] ) - def test_parametrize_without_ids(self, testdir): + def test_parametrize_without_ids(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1113,7 +1199,7 @@ class TestMetafuncFunctional(object): """ ) - def test_parametrize_with_None_in_ids(self, testdir): + def test_parametrize_with_None_in_ids(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1135,7 +1221,7 @@ class TestMetafuncFunctional(object): ] ) - def test_fixture_parametrized_empty_ids(self, testdir): + def test_fixture_parametrized_empty_ids(self, testdir: Testdir) -> None: """Fixtures parametrized with empty ids cause an internal error (#1849).""" testdir.makepyfile( """ @@ -1152,7 +1238,7 @@ class TestMetafuncFunctional(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 1 skipped *"]) - def test_parametrized_empty_ids(self, testdir): + def test_parametrized_empty_ids(self, testdir: Testdir) -> None: """Tests parametrized with empty ids cause an internal error (#1849).""" testdir.makepyfile( """ @@ -1166,13 +1252,13 @@ class TestMetafuncFunctional(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 1 skipped *"]) - def test_parametrized_ids_invalid_type(self, testdir): - """Tests parametrized with ids as non-strings (#1857).""" + def test_parametrized_ids_invalid_type(self, testdir: Testdir) -> None: + """Test error with non-strings/non-ints, without generator (#1857).""" testdir.makepyfile( """ import pytest - @pytest.mark.parametrize("x, expected", [(10, 20), (40, 80)], ids=(None, 2)) + @pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, type)) def test_ids_numbers(x,expected): assert x * 2 == expected """ @@ -1180,11 +1266,14 @@ class TestMetafuncFunctional(object): result = testdir.runpytest() result.stdout.fnmatch_lines( [ - "*In test_ids_numbers: ids must be list of strings, found: 2 (type: *'int'>)*" + "In test_ids_numbers: ids must be list of string/float/int/bool," + " found: <class 'type'> (type: <class 'type'>) at index 2" ] ) - def test_parametrize_with_identical_ids_get_unique_names(self, testdir): + def test_parametrize_with_identical_ids_get_unique_names( + self, testdir: Testdir + ) -> None: testdir.makepyfile( """ import pytest @@ -1203,13 +1292,15 @@ class TestMetafuncFunctional(object): ) @pytest.mark.parametrize(("scope", "length"), [("module", 2), ("function", 4)]) - def test_parametrize_scope_overrides(self, testdir, scope, length): + def test_parametrize_scope_overrides( + self, testdir: Testdir, scope: str, length: int + ) -> None: testdir.makepyfile( """ import pytest values = [] def pytest_generate_tests(metafunc): - if "arg" in metafunc.funcargnames: + if "arg" in metafunc.fixturenames: metafunc.parametrize("arg", [1,2], indirect=True, scope=%r) @pytest.fixture @@ -1228,7 +1319,7 @@ class TestMetafuncFunctional(object): reprec = testdir.inline_run() reprec.assertoutcome(passed=5) - def test_parametrize_issue323(self, testdir): + def test_parametrize_issue323(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1246,7 +1337,7 @@ class TestMetafuncFunctional(object): reprec = testdir.inline_run("--collect-only") assert not reprec.getcalls("pytest_internalerror") - def test_usefixtures_seen_in_generate_tests(self, testdir): + def test_usefixtures_seen_in_generate_tests(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1262,7 +1353,7 @@ class TestMetafuncFunctional(object): reprec = testdir.runpytest() reprec.assert_outcomes(passed=1) - def test_generate_tests_only_done_in_subdir(self, testdir): + def test_generate_tests_only_done_in_subdir(self, testdir: Testdir) -> None: sub1 = testdir.mkpydir("sub1") sub2 = testdir.mkpydir("sub2") sub1.join("conftest.py").write( @@ -1286,7 +1377,7 @@ class TestMetafuncFunctional(object): result = testdir.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) result.assert_outcomes(passed=3) - def test_generate_same_function_names_issue403(self, testdir): + def test_generate_same_function_names_issue403(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1304,35 +1395,38 @@ class TestMetafuncFunctional(object): reprec = testdir.runpytest() reprec.assert_outcomes(passed=4) - @pytest.mark.parametrize("attr", ["parametrise", "parameterize", "parameterise"]) - def test_parametrize_misspelling(self, testdir, attr): + def test_parametrize_misspelling(self, testdir: Testdir) -> None: """#463""" testdir.makepyfile( """ import pytest - @pytest.mark.{}("x", range(2)) + @pytest.mark.parametrise("x", range(2)) def test_foo(x): pass - """.format( - attr - ) + """ ) result = testdir.runpytest("--collectonly") result.stdout.fnmatch_lines( [ - "test_foo has '{}' mark, spelling should be 'parametrize'".format(attr), - "*1 error in*", + "collected 0 items / 1 error", + "", + "*= ERRORS =*", + "*_ ERROR collecting test_parametrize_misspelling.py _*", + "test_parametrize_misspelling.py:3: in <module>", + ' @pytest.mark.parametrise("x", range(2))', + "E Failed: Unknown 'parametrise' mark, did you mean 'parametrize'?", + "*! Interrupted: 1 error during collection !*", + "*= 1 error in *", ] ) -class TestMetafuncFunctionalAuto(object): - """ - Tests related to automatically find out the correct scope for parametrized tests (#1832). - """ +class TestMetafuncFunctionalAuto: + """Tests related to automatically find out the correct scope for + parametrized tests (#1832).""" - def test_parametrize_auto_scope(self, testdir): + def test_parametrize_auto_scope(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1354,7 +1448,7 @@ class TestMetafuncFunctionalAuto(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 3 passed *"]) - def test_parametrize_auto_scope_indirect(self, testdir): + def test_parametrize_auto_scope_indirect(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1377,7 +1471,7 @@ class TestMetafuncFunctionalAuto(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 3 passed *"]) - def test_parametrize_auto_scope_override_fixture(self, testdir): + def test_parametrize_auto_scope_override_fixture(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1394,7 +1488,7 @@ class TestMetafuncFunctionalAuto(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 2 passed *"]) - def test_parametrize_all_indirects(self, testdir): + def test_parametrize_all_indirects(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1421,11 +1515,13 @@ class TestMetafuncFunctionalAuto(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["* 3 passed *"]) - def test_parametrize_some_arguments_auto_scope(self, testdir, monkeypatch): + def test_parametrize_some_arguments_auto_scope( + self, testdir: Testdir, monkeypatch + ) -> None: """Integration test for (#3941)""" - class_fix_setup = [] + class_fix_setup = [] # type: List[object] monkeypatch.setattr(sys, "class_fix_setup", class_fix_setup, raising=False) - func_fix_setup = [] + func_fix_setup = [] # type: List[object] monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False) testdir.makepyfile( @@ -1454,7 +1550,7 @@ class TestMetafuncFunctionalAuto(object): assert func_fix_setup == [True] * 4 assert class_fix_setup == [10, 20] - def test_parametrize_issue634(self, testdir): + def test_parametrize_issue634(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1489,10 +1585,10 @@ class TestMetafuncFunctionalAuto(object): assert output.count("preparing foo-3") == 1 -class TestMarkersWithParametrization(object): +class TestMarkersWithParametrization: """#308""" - def test_simple_mark(self, testdir): + def test_simple_mark(self, testdir: Testdir) -> None: s = """ import pytest @@ -1513,7 +1609,7 @@ class TestMarkersWithParametrization(object): assert "bar" in items[1].keywords assert "bar" not in items[2].keywords - def test_select_based_on_mark(self, testdir): + def test_select_based_on_mark(self, testdir: Testdir) -> None: s = """ import pytest @@ -1526,34 +1622,13 @@ class TestMarkersWithParametrization(object): assert n + 1 == expected """ testdir.makepyfile(s) - rec = testdir.inline_run("-m", "foo", SHOW_PYTEST_WARNINGS_ARG) + rec = testdir.inline_run("-m", "foo") passed, skipped, fail = rec.listoutcomes() assert len(passed) == 1 assert len(skipped) == 0 assert len(fail) == 0 - @pytest.mark.xfail(reason="is this important to support??") - def test_nested_marks(self, testdir): - s = """ - import pytest - mastermark = pytest.mark.foo(pytest.mark.bar) - - @pytest.mark.parametrize(("n", "expected"), [ - (1, 2), - mastermark((1, 3)), - (2, 3), - ]) - def test_increment(n, expected): - assert n + 1 == expected - """ - items = testdir.getitems(s) - assert len(items) == 3 - for mark in ["foo", "bar"]: - assert mark not in items[0].keywords - assert mark in items[1].keywords - assert mark not in items[2].keywords - - def test_simple_xfail(self, testdir): + def test_simple_xfail(self, testdir: Testdir) -> None: s = """ import pytest @@ -1566,11 +1641,11 @@ class TestMarkersWithParametrization(object): assert n + 1 == expected """ testdir.makepyfile(s) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() # xfail is skip?? reprec.assertoutcome(passed=2, skipped=1) - def test_simple_xfail_single_argname(self, testdir): + def test_simple_xfail_single_argname(self, testdir: Testdir) -> None: s = """ import pytest @@ -1583,10 +1658,10 @@ class TestMarkersWithParametrization(object): assert n % 2 == 0 """ testdir.makepyfile(s) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() reprec.assertoutcome(passed=2, skipped=1) - def test_xfail_with_arg(self, testdir): + def test_xfail_with_arg(self, testdir: Testdir) -> None: s = """ import pytest @@ -1599,10 +1674,10 @@ class TestMarkersWithParametrization(object): assert n + 1 == expected """ testdir.makepyfile(s) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() reprec.assertoutcome(passed=2, skipped=1) - def test_xfail_with_kwarg(self, testdir): + def test_xfail_with_kwarg(self, testdir: Testdir) -> None: s = """ import pytest @@ -1615,10 +1690,10 @@ class TestMarkersWithParametrization(object): assert n + 1 == expected """ testdir.makepyfile(s) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() reprec.assertoutcome(passed=2, skipped=1) - def test_xfail_with_arg_and_kwarg(self, testdir): + def test_xfail_with_arg_and_kwarg(self, testdir: Testdir) -> None: s = """ import pytest @@ -1631,11 +1706,11 @@ class TestMarkersWithParametrization(object): assert n + 1 == expected """ testdir.makepyfile(s) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() reprec.assertoutcome(passed=2, skipped=1) @pytest.mark.parametrize("strict", [True, False]) - def test_xfail_passing_is_xpass(self, testdir, strict): + def test_xfail_passing_is_xpass(self, testdir: Testdir, strict: bool) -> None: s = """ import pytest @@ -1652,11 +1727,11 @@ class TestMarkersWithParametrization(object): strict=strict ) testdir.makepyfile(s) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() passed, failed = (2, 1) if strict else (3, 0) reprec.assertoutcome(passed=passed, failed=failed) - def test_parametrize_called_in_generate_tests(self, testdir): + def test_parametrize_called_in_generate_tests(self, testdir: Testdir) -> None: s = """ import pytest @@ -1676,10 +1751,10 @@ class TestMarkersWithParametrization(object): assert n + 1 == expected """ testdir.makepyfile(s) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() reprec.assertoutcome(passed=2, skipped=2) - def test_parametrize_ID_generation_string_int_works(self, testdir): + def test_parametrize_ID_generation_string_int_works(self, testdir: Testdir) -> None: """#290""" testdir.makepyfile( """ @@ -1698,7 +1773,7 @@ class TestMarkersWithParametrization(object): reprec.assertoutcome(passed=2) @pytest.mark.parametrize("strict", [True, False]) - def test_parametrize_marked_value(self, testdir, strict): + def test_parametrize_marked_value(self, testdir: Testdir, strict: bool) -> None: s = """ import pytest @@ -1722,7 +1797,7 @@ class TestMarkersWithParametrization(object): passed, failed = (0, 2) if strict else (2, 0) reprec.assertoutcome(passed=passed, failed=failed) - def test_pytest_make_parametrize_id(self, testdir): + def test_pytest_make_parametrize_id(self, testdir: Testdir) -> None: testdir.makeconftest( """ def pytest_make_parametrize_id(config, val): @@ -1741,7 +1816,7 @@ class TestMarkersWithParametrization(object): result = testdir.runpytest("-v") result.stdout.fnmatch_lines(["*test_func*0*PASS*", "*test_func*2*PASS*"]) - def test_pytest_make_parametrize_id_with_argname(self, testdir): + def test_pytest_make_parametrize_id_with_argname(self, testdir: Testdir) -> None: testdir.makeconftest( """ def pytest_make_parametrize_id(config, val, argname): @@ -1766,7 +1841,7 @@ class TestMarkersWithParametrization(object): ["*test_func_a*0*PASS*", "*test_func_a*2*PASS*", "*test_func_b*10*PASS*"] ) - def test_parametrize_positional_args(self, testdir): + def test_parametrize_positional_args(self, testdir: Testdir) -> None: testdir.makepyfile( """ import pytest @@ -1778,3 +1853,39 @@ class TestMarkersWithParametrization(object): ) result = testdir.runpytest() result.assert_outcomes(passed=1) + + def test_parametrize_iterator(self, testdir: Testdir) -> None: + testdir.makepyfile( + """ + import itertools + import pytest + + id_parametrize = pytest.mark.parametrize( + ids=("param%d" % i for i in itertools.count()) + ) + + @id_parametrize('y', ['a', 'b']) + def test1(y): + pass + + @id_parametrize('y', ['a', 'b']) + def test2(y): + pass + + @pytest.mark.parametrize("a, b", [(1, 2), (3, 4)], ids=itertools.count()) + def test_converted_to_str(a, b): + pass + """ + ) + result = testdir.runpytest("-vv", "-s") + result.stdout.fnmatch_lines( + [ + "test_parametrize_iterator.py::test1[param0] PASSED", + "test_parametrize_iterator.py::test1[param1] PASSED", + "test_parametrize_iterator.py::test2[param0] PASSED", + "test_parametrize_iterator.py::test2[param1] PASSED", + "test_parametrize_iterator.py::test_converted_to_str[0] PASSED", + "test_parametrize_iterator.py::test_converted_to_str[1] PASSED", + "*= 6 passed in *", + ] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/raises.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/raises.py index fa25d9f73ee..26931a37844 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/raises.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/raises.py @@ -1,54 +1,36 @@ -# -*- coding: utf-8 -*- +import re import sys -import six - import pytest -from _pytest.compat import dummy_context_manager from _pytest.outcomes import Failed -from _pytest.warning_types import PytestDeprecationWarning -class TestRaises(object): +class TestRaises: + def test_check_callable(self) -> None: + with pytest.raises(TypeError, match=r".* must be callable"): + pytest.raises(RuntimeError, "int('qwe')") # type: ignore[call-overload] + def test_raises(self): - source = "int('qwe')" - with pytest.warns(PytestDeprecationWarning): - excinfo = pytest.raises(ValueError, source) - code = excinfo.traceback[-1].frame.code - s = str(code.fullsource) - assert s == source - - def test_raises_exec(self): - with pytest.warns(PytestDeprecationWarning) as warninfo: - pytest.raises(ValueError, "a,x = []") - assert warninfo[0].filename == __file__ - - def test_raises_exec_correct_filename(self): - with pytest.warns(PytestDeprecationWarning): - excinfo = pytest.raises(ValueError, 'int("s")') - assert __file__ in excinfo.traceback[-1].path - - def test_raises_syntax_error(self): - with pytest.warns(PytestDeprecationWarning) as warninfo: - pytest.raises(SyntaxError, "qwe qwe qwe") - assert warninfo[0].filename == __file__ + excinfo = pytest.raises(ValueError, int, "qwe") + assert "invalid literal" in str(excinfo.value) def test_raises_function(self): - pytest.raises(ValueError, int, "hello") + excinfo = pytest.raises(ValueError, int, "hello") + assert "invalid literal" in str(excinfo.value) - def test_raises_callable_no_exception(self): - class A(object): + def test_raises_callable_no_exception(self) -> None: + class A: def __call__(self): pass try: pytest.raises(ValueError, A()) - except pytest.raises.Exception: + except pytest.fail.Exception: pass - def test_raises_falsey_type_error(self): + def test_raises_falsey_type_error(self) -> None: with pytest.raises(TypeError): - with pytest.raises(AssertionError, match=0): + with pytest.raises(AssertionError, match=0): # type: ignore[call-overload] raise AssertionError("ohai") def test_raises_repr_inflight(self): @@ -144,23 +126,23 @@ class TestRaises(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) - def test_noclass(self): + def test_noclass(self) -> None: with pytest.raises(TypeError): - pytest.raises("wrong", lambda: None) + pytest.raises("wrong", lambda: None) # type: ignore[call-overload] - def test_invalid_arguments_to_raises(self): + def test_invalid_arguments_to_raises(self) -> None: with pytest.raises(TypeError, match="unknown"): - with pytest.raises(TypeError, unknown="bogus"): + with pytest.raises(TypeError, unknown="bogus"): # type: ignore[call-overload] raise ValueError() def test_tuple(self): with pytest.raises((KeyError, ValueError)): raise KeyError("oops") - def test_no_raise_message(self): + def test_no_raise_message(self) -> None: try: pytest.raises(ValueError, int, "0") - except pytest.raises.Exception as e: + except pytest.fail.Exception as e: assert e.msg == "DID NOT RAISE {}".format(repr(ValueError)) else: assert False, "Expected pytest.raises.Exception" @@ -168,36 +150,32 @@ class TestRaises(object): try: with pytest.raises(ValueError): pass - except pytest.raises.Exception as e: + except pytest.fail.Exception as e: assert e.msg == "DID NOT RAISE {}".format(repr(ValueError)) else: assert False, "Expected pytest.raises.Exception" - def test_custom_raise_message(self): - message = "TEST_MESSAGE" - try: - with pytest.warns(PytestDeprecationWarning): - with pytest.raises(ValueError, message=message): - pass - except pytest.raises.Exception as e: - assert e.msg == message - else: - assert False, "Expected pytest.raises.Exception" - - @pytest.mark.parametrize("method", ["function", "with"]) + @pytest.mark.parametrize("method", ["function", "function_match", "with"]) def test_raises_cyclic_reference(self, method): - """ - Ensure pytest.raises does not leave a reference cycle (#1965). - """ + """Ensure pytest.raises does not leave a reference cycle (#1965).""" import gc - class T(object): + class T: def __call__(self): + # Early versions of Python 3.5 have some bug causing the + # __call__ frame to still refer to t even after everything + # is done. This makes the test pass for them. + if sys.version_info < (3, 5, 2): + del self raise ValueError t = T() + refcount = len(gc.get_referrers(t)) + if method == "function": pytest.raises(ValueError, t) + elif method == "function_match": + pytest.raises(ValueError, t).match("^$") else: with pytest.raises(ValueError): t() @@ -205,13 +183,9 @@ class TestRaises(object): # ensure both forms of pytest.raises don't leave exceptions in sys.exc_info() assert sys.exc_info() == (None, None, None) - del t + assert refcount == len(gc.get_referrers(t)) - # ensure the t instance is not stuck in a cyclic reference - for o in gc.get_objects(): - assert type(o) is not T - - def test_raises_match(self): + def test_raises_match(self) -> None: msg = r"with base \d+" with pytest.raises(ValueError, match=msg): int("asdf") @@ -221,13 +195,46 @@ class TestRaises(object): int("asdf") msg = "with base 16" - expr = r"Pattern '{}' not found in \"invalid literal for int\(\) with base 10: 'asdf'\"".format( + expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format( msg ) - with pytest.raises(AssertionError, match=expr): + with pytest.raises(AssertionError, match=re.escape(expr)): with pytest.raises(ValueError, match=msg): int("asdf", base=10) + # "match" without context manager. + pytest.raises(ValueError, int, "asdf").match("invalid literal") + with pytest.raises(AssertionError) as excinfo: + pytest.raises(ValueError, int, "asdf").match(msg) + assert str(excinfo.value) == expr + + pytest.raises(TypeError, int, match="invalid") + + def tfunc(match): + raise ValueError("match={}".format(match)) + + pytest.raises(ValueError, tfunc, match="asdf").match("match=asdf") + pytest.raises(ValueError, tfunc, match="").match("match=") + + def test_match_failure_string_quoting(self): + with pytest.raises(AssertionError) as excinfo: + with pytest.raises(AssertionError, match="'foo"): + raise AssertionError("'bar") + (msg,) = excinfo.value.args + assert msg == 'Regex pattern "\'foo" does not match "\'bar".' + + def test_match_failure_exact_string_message(self): + message = "Oh here is a message with (42) numbers in parameters" + with pytest.raises(AssertionError) as excinfo: + with pytest.raises(AssertionError, match=message): + raise AssertionError(message) + (msg,) = excinfo.value.args + assert msg == ( + "Regex pattern 'Oh here is a message with (42) numbers in " + "parameters' does not match 'Oh here is a message with (42) " + "numbers in parameters'. Did you mean to `re.escape()` the regex?" + ) + def test_raises_match_wrong_type(self): """Raising an exception with the wrong type and match= given. @@ -239,17 +246,14 @@ class TestRaises(object): int("asdf") def test_raises_exception_looks_iterable(self): - from six import add_metaclass - - class Meta(type(object)): + class Meta(type): def __getitem__(self, item): return 1 / 0 def __len__(self): return 1 - @add_metaclass(Meta) - class ClassLooksIterableException(Exception): + class ClassLooksIterableException(Exception, metaclass=Meta): pass with pytest.raises( @@ -258,68 +262,41 @@ class TestRaises(object): ): pytest.raises(ClassLooksIterableException, lambda: None) - def test_raises_with_raising_dunder_class(self): + def test_raises_with_raising_dunder_class(self) -> None: """Test current behavior with regard to exceptions via __class__ (#4284).""" class CrappyClass(Exception): - @property + # Type ignored because it's bypassed intentionally. + @property # type: ignore def __class__(self): assert False, "via __class__" - if six.PY2: - with pytest.raises(pytest.fail.Exception) as excinfo: - with pytest.raises(CrappyClass()): - pass - assert "DID NOT RAISE" in excinfo.value.args[0] + with pytest.raises(AssertionError) as excinfo: + with pytest.raises(CrappyClass()): # type: ignore[call-overload] + pass + assert "via __class__" in excinfo.value.args[0] - with pytest.raises(CrappyClass) as excinfo: - raise CrappyClass() - else: - with pytest.raises(AssertionError) as excinfo: - with pytest.raises(CrappyClass()): - pass - assert "via __class__" in excinfo.value.args[0] - - -class TestUnicodeHandling: - """Test various combinations of bytes and unicode with pytest.raises (#5478) - - https://github.com/pytest-dev/pytest/pull/5479#discussion_r298852433 - """ - - success = dummy_context_manager - py2_only = pytest.mark.skipif( - not six.PY2, reason="bytes in raises only supported in Python 2" - ) - - @pytest.mark.parametrize( - "message, match, expectation", - [ - (u"\u2603", u"\u2603", success()), - (u"\u2603", u"\u2603foo", pytest.raises(AssertionError)), - pytest.param(b"hello", b"hello", success(), marks=py2_only), - pytest.param( - b"hello", b"world", pytest.raises(AssertionError), marks=py2_only - ), - pytest.param(u"hello", b"hello", success(), marks=py2_only), - pytest.param( - u"hello", b"world", pytest.raises(AssertionError), marks=py2_only - ), - pytest.param( - u"😊".encode("UTF-8"), - b"world", - pytest.raises(AssertionError), - marks=py2_only, - ), - pytest.param( - u"world", - u"😊".encode("UTF-8"), - pytest.raises(AssertionError), - marks=py2_only, - ), - ], - ) - def test_handling(self, message, match, expectation): - with expectation: - with pytest.raises(RuntimeError, match=match): - raise RuntimeError(message) + def test_raises_context_manager_with_kwargs(self): + with pytest.raises(TypeError) as excinfo: + with pytest.raises(Exception, foo="bar"): # type: ignore[call-overload] + pass + assert "Unexpected keyword arguments" in str(excinfo.value) + + def test_expected_exception_is_not_a_baseexception(self) -> None: + with pytest.raises(TypeError) as excinfo: + with pytest.raises("hello"): # type: ignore[call-overload] + pass # pragma: no cover + assert "must be a BaseException type, not str" in str(excinfo.value) + + class NotAnException: + pass + + with pytest.raises(TypeError) as excinfo: + with pytest.raises(NotAnException): # type: ignore[type-var] + pass # pragma: no cover + assert "must be a BaseException type, not NotAnException" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + with pytest.raises(("hello", NotAnException)): # type: ignore[arg-type] + pass # pragma: no cover + assert "must be a BaseException type, not str" in str(excinfo.value) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/setup_only.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/setup_only.py deleted file mode 100644 index ba4ceb00fce..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/setup_only.py +++ /dev/null @@ -1,270 +0,0 @@ -# -*- coding: utf-8 -*- -import pytest - - -@pytest.fixture(params=["--setup-only", "--setup-plan", "--setup-show"], scope="module") -def mode(request): - return request.param - - -def test_show_only_active_fixtures(testdir, mode): - p = testdir.makepyfile( - ''' - import pytest - @pytest.fixture - def _arg0(): - """hidden arg0 fixture""" - @pytest.fixture - def arg1(): - """arg1 docstring""" - def test_arg1(arg1): - pass - ''' - ) - - result = testdir.runpytest(mode, p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - ["*SETUP F arg1*", "*test_arg1 (fixtures used: arg1)*", "*TEARDOWN F arg1*"] - ) - assert "_arg0" not in result.stdout.str() - - -def test_show_different_scopes(testdir, mode): - p = testdir.makepyfile( - ''' - import pytest - @pytest.fixture - def arg_function(): - """function scoped fixture""" - @pytest.fixture(scope='session') - def arg_session(): - """session scoped fixture""" - def test_arg1(arg_session, arg_function): - pass - ''' - ) - - result = testdir.runpytest(mode, p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - [ - "SETUP S arg_session*", - "*SETUP F arg_function*", - "*test_arg1 (fixtures used: arg_function, arg_session)*", - "*TEARDOWN F arg_function*", - "TEARDOWN S arg_session*", - ] - ) - - -def test_show_nested_fixtures(testdir, mode): - testdir.makeconftest( - ''' - import pytest - @pytest.fixture(scope='session') - def arg_same(): - """session scoped fixture""" - ''' - ) - p = testdir.makepyfile( - ''' - import pytest - @pytest.fixture(scope='function') - def arg_same(arg_same): - """function scoped fixture""" - def test_arg1(arg_same): - pass - ''' - ) - - result = testdir.runpytest(mode, p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - [ - "SETUP S arg_same*", - "*SETUP F arg_same (fixtures used: arg_same)*", - "*test_arg1 (fixtures used: arg_same)*", - "*TEARDOWN F arg_same*", - "TEARDOWN S arg_same*", - ] - ) - - -def test_show_fixtures_with_autouse(testdir, mode): - p = testdir.makepyfile( - ''' - import pytest - @pytest.fixture - def arg_function(): - """function scoped fixture""" - @pytest.fixture(scope='session', autouse=True) - def arg_session(): - """session scoped fixture""" - def test_arg1(arg_function): - pass - ''' - ) - - result = testdir.runpytest(mode, p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - [ - "SETUP S arg_session*", - "*SETUP F arg_function*", - "*test_arg1 (fixtures used: arg_function, arg_session)*", - ] - ) - - -def test_show_fixtures_with_parameters(testdir, mode): - testdir.makeconftest( - ''' - import pytest - @pytest.fixture(scope='session', params=['foo', 'bar']) - def arg_same(): - """session scoped fixture""" - ''' - ) - p = testdir.makepyfile( - ''' - import pytest - @pytest.fixture(scope='function') - def arg_other(arg_same): - """function scoped fixture""" - def test_arg1(arg_other): - pass - ''' - ) - - result = testdir.runpytest(mode, p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - [ - "SETUP S arg_same?foo?", - "TEARDOWN S arg_same?foo?", - "SETUP S arg_same?bar?", - "TEARDOWN S arg_same?bar?", - ] - ) - - -def test_show_fixtures_with_parameter_ids(testdir, mode): - testdir.makeconftest( - ''' - import pytest - @pytest.fixture( - scope='session', params=['foo', 'bar'], ids=['spam', 'ham']) - def arg_same(): - """session scoped fixture""" - ''' - ) - p = testdir.makepyfile( - ''' - import pytest - @pytest.fixture(scope='function') - def arg_other(arg_same): - """function scoped fixture""" - def test_arg1(arg_other): - pass - ''' - ) - - result = testdir.runpytest(mode, p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - ["SETUP S arg_same?spam?", "SETUP S arg_same?ham?"] - ) - - -def test_show_fixtures_with_parameter_ids_function(testdir, mode): - p = testdir.makepyfile( - """ - import pytest - @pytest.fixture(params=['foo', 'bar'], ids=lambda p: p.upper()) - def foobar(): - pass - def test_foobar(foobar): - pass - """ - ) - - result = testdir.runpytest(mode, p) - assert result.ret == 0 - - result.stdout.fnmatch_lines(["*SETUP F foobar?FOO?", "*SETUP F foobar?BAR?"]) - - -def test_dynamic_fixture_request(testdir): - p = testdir.makepyfile( - """ - import pytest - @pytest.fixture() - def dynamically_requested_fixture(): - pass - @pytest.fixture() - def dependent_fixture(request): - request.getfixturevalue('dynamically_requested_fixture') - def test_dyn(dependent_fixture): - pass - """ - ) - - result = testdir.runpytest("--setup-only", p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - [ - "*SETUP F dynamically_requested_fixture", - "*TEARDOWN F dynamically_requested_fixture", - ] - ) - - -def test_capturing(testdir): - p = testdir.makepyfile( - """ - import pytest, sys - @pytest.fixture() - def one(): - sys.stdout.write('this should be captured') - sys.stderr.write('this should also be captured') - @pytest.fixture() - def two(one): - assert 0 - def test_capturing(two): - pass - """ - ) - - result = testdir.runpytest("--setup-only", p) - result.stdout.fnmatch_lines( - ["this should be captured", "this should also be captured"] - ) - - -def test_show_fixtures_and_execute_test(testdir): - """ Verifies that setups are shown and tests are executed. """ - p = testdir.makepyfile( - """ - import pytest - @pytest.fixture - def arg(): - assert True - def test_arg(arg): - assert False - """ - ) - - result = testdir.runpytest("--setup-show", p) - assert result.ret == 1 - - result.stdout.fnmatch_lines( - ["*SETUP F arg*", "*test_arg (fixtures used: arg)F*", "*TEARDOWN F arg*"] - ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/setup_plan.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/setup_plan.py deleted file mode 100644 index ad64e42c29c..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/setup_plan.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -def test_show_fixtures_and_test(testdir): - """ Verifies that fixtures are not executed. """ - p = testdir.makepyfile( - """ - import pytest - @pytest.fixture - def arg(): - assert False - def test_arg(arg): - assert False - """ - ) - - result = testdir.runpytest("--setup-plan", p) - assert result.ret == 0 - - result.stdout.fnmatch_lines( - ["*SETUP F arg*", "*test_arg (fixtures used: arg)", "*TEARDOWN F arg*"] - ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/show_fixtures_per_test.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/show_fixtures_per_test.py index e14344d4ebf..ef841819d09 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/show_fixtures_per_test.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/python/show_fixtures_per_test.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- - - def test_no_items_should_not_show_output(testdir): result = testdir.runpytest("--fixtures-per-test") - assert "fixtures used by" not in result.stdout.str() + result.stdout.no_fnmatch_line("*fixtures used by*") assert result.ret == 0 @@ -33,7 +30,7 @@ def test_fixtures_in_module(testdir): " arg1 docstring", ] ) - assert "_arg0" not in result.stdout.str() + result.stdout.no_fnmatch_line("*_arg0*") def test_fixtures_in_conftest(testdir): diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py index da7758e8e9e..9cab242c0e0 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_argcomplete.py @@ -1,14 +1,9 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import subprocess import sys import pytest -# test for _argcomplete but not specific for any application +# Test for _argcomplete but not specific for any application. def equal_with_bash(prefix, ffc, fc, out=None): @@ -23,32 +18,20 @@ def equal_with_bash(prefix, ffc, fc, out=None): return retval -# copied from argcomplete.completers as import from there -# also pulls in argcomplete.__init__ which opens filedescriptor 9 -# this gives an IOError at the end of testrun +# Copied from argcomplete.completers as import from there. +# Also pulls in argcomplete.__init__ which opens filedescriptor 9. +# This gives an OSError at the end of testrun. def _wrapcall(*args, **kargs): try: - if sys.version_info > (2, 7): - return subprocess.check_output(*args, **kargs).decode().splitlines() - if "stdout" in kargs: - raise ValueError("stdout argument not allowed, it will be overridden.") - process = subprocess.Popen(stdout=subprocess.PIPE, *args, **kargs) - output, unused_err = process.communicate() - retcode = process.poll() - if retcode: - cmd = kargs.get("args") - if cmd is None: - cmd = args[0] - raise subprocess.CalledProcessError(retcode, cmd) - return output.decode().splitlines() + return subprocess.check_output(*args, **kargs).decode().splitlines() except subprocess.CalledProcessError: return [] -class FilesCompleter(object): - "File completer class, optionally takes a list of allowed extensions" +class FilesCompleter: + """File completer class, optionally takes a list of allowed extensions.""" def __init__(self, allowednames=(), directories=True): # Fix if someone passes in a string instead of a list @@ -90,7 +73,7 @@ class FilesCompleter(object): return completion -class TestArgComplete(object): +class TestArgComplete: @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") def test_compare_with_compgen(self, tmpdir): from _pytest._argcomplete import FastFilesCompleter @@ -108,9 +91,7 @@ class TestArgComplete(object): @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") def test_remove_dir_prefix(self): - """this is not compatible with compgen but it is with bash itself: - ls /usr/<TAB> - """ + """This is not compatible with compgen but it is with bash itself: ls /usr/<TAB>.""" from _pytest._argcomplete import FastFilesCompleter ffc = FastFilesCompleter() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py index 225362e64ef..1cb63a329f6 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertion.py @@ -1,13 +1,11 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import collections.abc import sys import textwrap +from typing import Any +from typing import List +from typing import Optional import attr -import six import _pytest.assertion as plugin import pytest @@ -16,28 +14,22 @@ from _pytest.assertion import truncate from _pytest.assertion import util from _pytest.compat import ATTRS_EQ_FIELD -PY3 = sys.version_info >= (3, 0) - - -def mock_config(): - class Config(object): - verbose = False +def mock_config(verbose=0): + class Config: def getoption(self, name): if name == "verbose": - return self.verbose + return verbose raise KeyError("Not mocked out: %s" % name) return Config() -class TestImportHookInstallation(object): +class TestImportHookInstallation: @pytest.mark.parametrize("initial_conftest", [True, False]) @pytest.mark.parametrize("mode", ["plain", "rewrite"]) def test_conftest_assertion_rewrite(self, testdir, initial_conftest, mode): - """Test that conftest files are using assertion rewrite on import. - (#1619) - """ + """Test that conftest files are using assertion rewrite on import (#1619).""" testdir.tmpdir.join("foo/tests").ensure(dir=1) conftest_path = "conftest.py" if initial_conftest else "foo/conftest.py" contents = { @@ -79,7 +71,23 @@ class TestImportHookInstallation(object): """ ) result = testdir.runpytest_subprocess() - result.stdout.fnmatch_lines(["*assert 1 == 0*"]) + result.stdout.fnmatch_lines( + [ + "> r.assertoutcome(passed=1)", + "E AssertionError: ([[][]], [[][]], [[]<TestReport *>[]])*", + "E assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}", + "E Omitting 1 identical items, use -vv to show", + "E Differing items:", + "E Use -v to get the full diff", + ] + ) + # XXX: unstable output. + result.stdout.fnmatch_lines_random( + [ + "E {'failed': 1} != {'failed': 0}", + "E {'passed': 0} != {'passed': 1}", + ] + ) @pytest.mark.parametrize("mode", ["plain", "rewrite"]) def test_pytest_plugins_rewrite(self, testdir, mode): @@ -145,8 +153,8 @@ class TestImportHookInstallation(object): "hamster.py": "", "test_foo.py": """\ def test_foo(pytestconfig): - assert pytestconfig.pluginmanager.rewrite_hook.find_module('ham') is not None - assert pytestconfig.pluginmanager.rewrite_hook.find_module('hamster') is None + assert pytestconfig.pluginmanager.rewrite_hook.find_spec('ham') is not None + assert pytestconfig.pluginmanager.rewrite_hook.find_spec('hamster') is None """, } testdir.makepyfile(**contents) @@ -269,15 +277,15 @@ class TestImportHookInstallation(object): ] ) - def test_register_assert_rewrite_checks_types(self): + def test_register_assert_rewrite_checks_types(self) -> None: with pytest.raises(TypeError): - pytest.register_assert_rewrite(["pytest_tests_internal_non_existing"]) + pytest.register_assert_rewrite(["pytest_tests_internal_non_existing"]) # type: ignore pytest.register_assert_rewrite( "pytest_tests_internal_non_existing", "pytest_tests_internal_non_existing2" ) -class TestBinReprIntegration(object): +class TestBinReprIntegration: def test_pytest_assertrepr_compare_called(self, testdir): testdir.makeconftest( """ @@ -303,131 +311,273 @@ class TestBinReprIntegration(object): result.stdout.fnmatch_lines(["*test_hello*FAIL*", "*test_check*PASS*"]) -def callequal(left, right, verbose=False): - config = mock_config() - config.verbose = verbose - return plugin.pytest_assertrepr_compare(config, "==", left, right) +def callop(op: str, left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: + config = mock_config(verbose=verbose) + return plugin.pytest_assertrepr_compare(config, op, left, right) + + +def callequal(left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: + return callop("==", left, right, verbose) -class TestAssert_reprcompare(object): +class TestAssert_reprcompare: def test_different_types(self): assert callequal([0, 1], "foo") is None - def test_summary(self): - summary = callequal([0, 1], [0, 2])[0] + def test_summary(self) -> None: + lines = callequal([0, 1], [0, 2]) + assert lines is not None + summary = lines[0] assert len(summary) < 65 def test_text_diff(self): - diff = callequal("spam", "eggs")[1:] - assert "- spam" in diff - assert "+ eggs" in diff + assert callequal("spam", "eggs") == [ + "'spam' == 'eggs'", + "- eggs", + "+ spam", + ] - def test_text_skipping(self): + def test_text_skipping(self) -> None: lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs") + assert lines is not None assert "Skipping" in lines[1] for line in lines: assert "a" * 50 not in line - def test_text_skipping_verbose(self): - lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs", verbose=True) - assert "- " + "a" * 50 + "spam" in lines - assert "+ " + "a" * 50 + "eggs" in lines + def test_text_skipping_verbose(self) -> None: + lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs", verbose=1) + assert lines is not None + assert "- " + "a" * 50 + "eggs" in lines + assert "+ " + "a" * 50 + "spam" in lines - def test_multiline_text_diff(self): + def test_multiline_text_diff(self) -> None: left = "foo\nspam\nbar" right = "foo\neggs\nbar" diff = callequal(left, right) - assert "- spam" in diff - assert "+ eggs" in diff + assert diff is not None + assert "- eggs" in diff + assert "+ spam" in diff + + def test_bytes_diff_normal(self): + """Check special handling for bytes diff (#5260)""" + diff = callequal(b"spam", b"eggs") + + assert diff == [ + "b'spam' == b'eggs'", + "At index 0 diff: b's' != b'e'", + "Use -v to get the full diff", + ] + + def test_bytes_diff_verbose(self): + """Check special handling for bytes diff (#5260)""" + diff = callequal(b"spam", b"eggs", verbose=1) + assert diff == [ + "b'spam' == b'eggs'", + "At index 0 diff: b's' != b'e'", + "Full diff:", + "- b'eggs'", + "+ b'spam'", + ] - def test_list(self): + def test_list(self) -> None: expl = callequal([0, 1], [0, 2]) + assert expl is not None assert len(expl) > 1 @pytest.mark.parametrize( ["left", "right", "expected"], [ - ( + pytest.param( [0, 1], [0, 2], """ Full diff: - - [0, 1] + - [0, 2] ? ^ - + [0, 2] + + [0, 1] ? ^ """, + id="lists", ), - ( + pytest.param( {0: 1}, {0: 2}, """ Full diff: - - {0: 1} + - {0: 2} ? ^ - + {0: 2} + + {0: 1} ? ^ """, + id="dicts", ), - ( + pytest.param( {0, 1}, {0, 2}, """ Full diff: - - set([0, 1]) - ? ^ - + set([0, 2]) - ? ^ - """ - if not PY3 - else """ - Full diff: - - {0, 1} + - {0, 2} ? ^ - + {0, 2} + + {0, 1} ? ^ """, + id="sets", ), ], ) - def test_iterable_full_diff(self, left, right, expected): + def test_iterable_full_diff(self, left, right, expected) -> None: """Test the full diff assertion failure explanation. When verbose is False, then just a -v notice to get the diff is rendered, when verbose is True, then ndiff of the pprint is returned. """ - expl = callequal(left, right, verbose=False) + expl = callequal(left, right, verbose=0) + assert expl is not None assert expl[-1] == "Use -v to get the full diff" - expl = "\n".join(callequal(left, right, verbose=True)) - assert expl.endswith(textwrap.dedent(expected).strip()) + verbose_expl = callequal(left, right, verbose=1) + assert verbose_expl is not None + assert "\n".join(verbose_expl).endswith(textwrap.dedent(expected).strip()) - def test_list_different_lengths(self): + def test_list_different_lengths(self) -> None: expl = callequal([0, 1], [0, 1, 2]) + assert expl is not None assert len(expl) > 1 expl = callequal([0, 1, 2], [0, 1]) + assert expl is not None assert len(expl) > 1 - def test_dict(self): + def test_list_wrap_for_multiple_lines(self): + long_d = "d" * 80 + l1 = ["a", "b", "c"] + l2 = ["a", "b", "c", long_d] + diff = callequal(l1, l2, verbose=True) + assert diff == [ + "['a', 'b', 'c'] == ['a', 'b', 'c...dddddddddddd']", + "Right contains one more item: '" + long_d + "'", + "Full diff:", + " [", + " 'a',", + " 'b',", + " 'c',", + "- '" + long_d + "',", + " ]", + ] + + diff = callequal(l2, l1, verbose=True) + assert diff == [ + "['a', 'b', 'c...dddddddddddd'] == ['a', 'b', 'c']", + "Left contains one more item: '" + long_d + "'", + "Full diff:", + " [", + " 'a',", + " 'b',", + " 'c',", + "+ '" + long_d + "',", + " ]", + ] + + def test_list_wrap_for_width_rewrap_same_length(self): + long_a = "a" * 30 + long_b = "b" * 30 + long_c = "c" * 30 + l1 = [long_a, long_b, long_c] + l2 = [long_b, long_c, long_a] + diff = callequal(l1, l2, verbose=True) + assert diff == [ + "['aaaaaaaaaaa...cccccccccccc'] == ['bbbbbbbbbbb...aaaaaaaaaaaa']", + "At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", + "Full diff:", + " [", + "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", + " 'cccccccccccccccccccccccccccccc',", + "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", + " ]", + ] + + def test_list_dont_wrap_strings(self): + long_a = "a" * 10 + l1 = ["a"] + [long_a for _ in range(0, 7)] + l2 = ["should not get wrapped"] + diff = callequal(l1, l2, verbose=True) + assert diff == [ + "['a', 'aaaaaa...aaaaaaa', ...] == ['should not get wrapped']", + "At index 0 diff: 'a' != 'should not get wrapped'", + "Left contains 7 more items, first extra item: 'aaaaaaaaaa'", + "Full diff:", + " [", + "- 'should not get wrapped',", + "+ 'a',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + "+ 'aaaaaaaaaa',", + " ]", + ] + + def test_dict_wrap(self): + d1 = {"common": 1, "env": {"env1": 1, "env2": 2}} + d2 = {"common": 1, "env": {"env1": 1}} + + diff = callequal(d1, d2, verbose=True) + assert diff == [ + "{'common': 1,...1, 'env2': 2}} == {'common': 1,...: {'env1': 1}}", + "Omitting 1 identical items, use -vv to show", + "Differing items:", + "{'env': {'env1': 1, 'env2': 2}} != {'env': {'env1': 1}}", + "Full diff:", + "- {'common': 1, 'env': {'env1': 1}}", + "+ {'common': 1, 'env': {'env1': 1, 'env2': 2}}", + "? +++++++++++", + ] + + long_a = "a" * 80 + sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 2}} + d1 = {"env": {"sub": sub}} + d2 = {"env": {"sub": sub}, "new": 1} + diff = callequal(d1, d2, verbose=True) + assert diff == [ + "{'env': {'sub... wrapped '}}}} == {'env': {'sub...}}}, 'new': 1}", + "Omitting 1 identical items, use -vv to show", + "Right contains 1 more item:", + "{'new': 1}", + "Full diff:", + " {", + " 'env': {'sub': {'long_a': '" + long_a + "',", + " 'sub1': {'long_a': 'substring that gets wrapped substring '", + " 'that gets wrapped '}}},", + "- 'new': 1,", + " }", + ] + + def test_dict(self) -> None: expl = callequal({"a": 0}, {"a": 1}) + assert expl is not None assert len(expl) > 1 - def test_dict_omitting(self): + def test_dict_omitting(self) -> None: lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}) + assert lines is not None assert lines[1].startswith("Omitting 1 identical item") assert "Common items" not in lines for line in lines[1:]: assert "b" not in line - def test_dict_omitting_with_verbosity_1(self): - """ Ensure differing items are visible for verbosity=1 (#1512) """ + def test_dict_omitting_with_verbosity_1(self) -> None: + """Ensure differing items are visible for verbosity=1 (#1512).""" lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=1) + assert lines is not None assert lines[1].startswith("Omitting 1 identical item") assert lines[2].startswith("Differing items") assert lines[3] == "{'a': 0} != {'a': 1}" assert "Common items" not in lines - def test_dict_omitting_with_verbosity_2(self): + def test_dict_omitting_with_verbosity_2(self) -> None: lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=2) + assert lines is not None assert lines[1].startswith("Common items:") assert "Omitting" not in lines[1] assert lines[2] == "{'b': 1}" @@ -441,8 +591,8 @@ class TestAssert_reprcompare(object): "Right contains 2 more items:", "{'b': 1, 'c': 2}", "Full diff:", - "- {'a': 0}", - "+ {'b': 1, 'c': 2}", + "- {'b': 1, 'c': 2}", + "+ {'a': 0}", ] lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2) assert lines == [ @@ -452,8 +602,8 @@ class TestAssert_reprcompare(object): "Right contains 1 more item:", "{'a': 0}", "Full diff:", - "- {'b': 1, 'c': 2}", - "+ {'a': 0}", + "- {'a': 0}", + "+ {'b': 1, 'c': 2}", ] def test_sequence_different_items(self): @@ -463,8 +613,8 @@ class TestAssert_reprcompare(object): "At index 0 diff: 1 != 3", "Right contains one more item: 5", "Full diff:", - "- (1, 2)", - "+ (3, 4, 5)", + "- (3, 4, 5)", + "+ (1, 2)", ] lines = callequal((1, 2, 3), (4,), verbose=2) assert lines == [ @@ -472,28 +622,24 @@ class TestAssert_reprcompare(object): "At index 0 diff: 1 != 4", "Left contains 2 more items, first extra item: 2", "Full diff:", - "- (1, 2, 3)", - "+ (4,)", + "- (4,)", + "+ (1, 2, 3)", ] - def test_set(self): + def test_set(self) -> None: expl = callequal({0, 1}, {0, 2}) + assert expl is not None assert len(expl) > 1 - def test_frozenzet(self): + def test_frozenzet(self) -> None: expl = callequal(frozenset([0, 1]), {0, 2}) + assert expl is not None assert len(expl) > 1 - def test_Sequence(self): - if sys.version_info >= (3, 3): - import collections.abc as collections_abc - else: - import collections as collections_abc - if not hasattr(collections_abc, "MutableSequence"): - pytest.skip("cannot import MutableSequence") - MutableSequence = collections_abc.MutableSequence - - class TestSequence(MutableSequence): # works with a Sequence subclass + def test_Sequence(self) -> None: + # Test comparing with a Sequence subclass. + # TODO(py36): Inherit from typing.MutableSequence[int]. + class TestSequence(collections.abc.MutableSequence): # type: ignore[type-arg] def __init__(self, iterable): self.elements = list(iterable) @@ -513,15 +659,18 @@ class TestAssert_reprcompare(object): pass expl = callequal(TestSequence([0, 1]), list([0, 2])) + assert expl is not None assert len(expl) > 1 - def test_list_tuples(self): + def test_list_tuples(self) -> None: expl = callequal([], [(1, 2)]) + assert expl is not None assert len(expl) > 1 expl = callequal([(1, 2)], []) + assert expl is not None assert len(expl) > 1 - def test_repr_verbose(self): + def test_repr_verbose(self) -> None: class Nums: def __init__(self, nums): self.nums = nums @@ -538,28 +687,37 @@ class TestAssert_reprcompare(object): assert callequal(nums_x, nums_y) is None expl = callequal(nums_x, nums_y, verbose=1) - assert "-" + repr(nums_x) in expl - assert "+" + repr(nums_y) in expl + assert expl is not None + assert "+" + repr(nums_x) in expl + assert "-" + repr(nums_y) in expl expl = callequal(nums_x, nums_y, verbose=2) - assert "-" + repr(nums_x) in expl - assert "+" + repr(nums_y) in expl + assert expl is not None + assert "+" + repr(nums_x) in expl + assert "-" + repr(nums_y) in expl - def test_list_bad_repr(self): - class A(object): + def test_list_bad_repr(self) -> None: + class A: def __repr__(self): raise ValueError(42) expl = callequal([], [A()]) + assert expl is not None assert "ValueError" in "".join(expl) - expl = callequal({}, {"1": A()}) - assert "faulty" in "".join(expl) + expl = callequal({}, {"1": A()}, verbose=2) + assert expl is not None + assert expl[0].startswith("{} == <[ValueError") + assert "raised in repr" in expl[0] + assert expl[1:] == [ + "(pytest_assertion plugin: representation of details failed:" + " {}:{}: ValueError: 42.".format( + __file__, A.__repr__.__code__.co_firstlineno + 1 + ), + " Probably an object has a faulty __repr__.)", + ] def test_one_repr_empty(self): - """ - the faulty empty string repr did trigger - an unbound local error in _diff_text - """ + """The faulty empty string repr did trigger an unbound local error in _diff_text.""" class A(str): def __repr__(self): @@ -568,17 +726,17 @@ class TestAssert_reprcompare(object): expl = callequal(A(), "") assert not expl - def test_repr_no_exc(self): - expl = " ".join(callequal("foo", "bar")) - assert "raised in repr()" not in expl + def test_repr_no_exc(self) -> None: + expl = callequal("foo", "bar") + assert expl is not None + assert "raised in repr()" not in " ".join(expl) def test_unicode(self): - left = u"£€" - right = u"£" - expl = callequal(left, right) - assert expl[0] == u"'£€' == '£'" - assert expl[1] == u"- £€" - assert expl[2] == u"+ £" + assert callequal("£€", "£") == [ + "'£€' == '£'", + "- £", + "+ £€", + ] def test_nonascii_text(self): """ @@ -591,26 +749,24 @@ class TestAssert_reprcompare(object): return "\xff" expl = callequal(A(), "1") - if PY3: - assert expl == ["ÿ == '1'", "+ 1"] - else: - assert expl == [u"\ufffd == '1'", u"+ 1"] + assert expl == ["ÿ == '1'", "- 1"] def test_format_nonascii_explanation(self): assert util.format_explanation("λ") - def test_mojibake(self): + def test_mojibake(self) -> None: # issue 429 left = b"e" right = b"\xc3\xa9" expl = callequal(left, right) + assert expl is not None for line in expl: - assert isinstance(line, six.text_type) - msg = u"\n".join(expl) + assert isinstance(line, str) + msg = "\n".join(expl) assert msg -class TestAssert_reprcompare_dataclass(object): +class TestAssert_reprcompare_dataclass: @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") def test_dataclasses(self, testdir): p = testdir.copy_example("dataclasses/test_compare_dataclasses.py") @@ -618,10 +774,68 @@ class TestAssert_reprcompare_dataclass(object): result.assert_outcomes(failed=1, passed=0) result.stdout.fnmatch_lines( [ - "*Omitting 1 identical items, use -vv to show*", - "*Differing attributes:*", - "*field_b: 'b' != 'c'*", - ] + "E Omitting 1 identical items, use -vv to show", + "E Differing attributes:", + "E ['field_b']", + "E ", + "E Drill down into differing attribute field_b:", + "E field_b: 'b' != 'c'...", + "E ", + "E ...Full output truncated (3 lines hidden), use '-vv' to show", + ], + consecutive=True, + ) + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") + def test_recursive_dataclasses(self, testdir): + p = testdir.copy_example("dataclasses/test_compare_recursive_dataclasses.py") + result = testdir.runpytest(p) + result.assert_outcomes(failed=1, passed=0) + result.stdout.fnmatch_lines( + [ + "E Omitting 1 identical items, use -vv to show", + "E Differing attributes:", + "E ['g', 'h', 'j']", + "E ", + "E Drill down into differing attribute g:", + "E g: S(a=10, b='ten') != S(a=20, b='xxx')...", + "E ", + "E ...Full output truncated (52 lines hidden), use '-vv' to show", + ], + consecutive=True, + ) + + @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") + def test_recursive_dataclasses_verbose(self, testdir): + p = testdir.copy_example("dataclasses/test_compare_recursive_dataclasses.py") + result = testdir.runpytest(p, "-vv") + result.assert_outcomes(failed=1, passed=0) + result.stdout.fnmatch_lines( + [ + "E Matching attributes:", + "E ['i']", + "E Differing attributes:", + "E ['g', 'h', 'j']", + "E ", + "E Drill down into differing attribute g:", + "E g: S(a=10, b='ten') != S(a=20, b='xxx')", + "E ", + "E Differing attributes:", + "E ['a', 'b']", + "E ", + "E Drill down into differing attribute a:", + "E a: 10 != 20", + "E +10", + "E -20", + "E ", + "E Drill down into differing attribute b:", + "E b: 'ten' != 'xxx'", + "E - xxx", + "E + ten", + "E ", + "E Drill down into differing attribute h:", + ], + consecutive=True, ) @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+") @@ -655,10 +869,10 @@ class TestAssert_reprcompare_dataclass(object): result.assert_outcomes(failed=0, passed=1) -class TestAssert_reprcompare_attrsclass(object): - def test_attrs(self): +class TestAssert_reprcompare_attrsclass: + def test_attrs(self) -> None: @attr.s - class SimpleDataObject(object): + class SimpleDataObject: field_a = attr.ib() field_b = attr.ib() @@ -666,14 +880,55 @@ class TestAssert_reprcompare_attrsclass(object): right = SimpleDataObject(1, "c") lines = callequal(left, right) - assert lines[1].startswith("Omitting 1 identical item") + assert lines is not None + assert lines[2].startswith("Omitting 1 identical item") assert "Matching attributes" not in lines - for line in lines[1:]: + for line in lines[2:]: assert "field_a" not in line - def test_attrs_verbose(self): + def test_attrs_recursive(self) -> None: + @attr.s + class OtherDataObject: + field_c = attr.ib() + field_d = attr.ib() + + @attr.s + class SimpleDataObject: + field_a = attr.ib() + field_b = attr.ib() + + left = SimpleDataObject(OtherDataObject(1, "a"), "b") + right = SimpleDataObject(OtherDataObject(1, "b"), "b") + + lines = callequal(left, right) + assert lines is not None + assert "Matching attributes" not in lines + for line in lines[1:]: + assert "field_b:" not in line + assert "field_c:" not in line + + def test_attrs_recursive_verbose(self) -> None: + @attr.s + class OtherDataObject: + field_c = attr.ib() + field_d = attr.ib() + @attr.s - class SimpleDataObject(object): + class SimpleDataObject: + field_a = attr.ib() + field_b = attr.ib() + + left = SimpleDataObject(OtherDataObject(1, "a"), "b") + right = SimpleDataObject(OtherDataObject(1, "b"), "b") + + lines = callequal(left, right) + assert lines is not None + # indentation in output because of nested object structure + assert " field_d: 'a' != 'b'" in lines + + def test_attrs_verbose(self) -> None: + @attr.s + class SimpleDataObject: field_a = attr.ib() field_b = attr.ib() @@ -681,34 +936,37 @@ class TestAssert_reprcompare_attrsclass(object): right = SimpleDataObject(1, "c") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Matching attributes:") - assert "Omitting" not in lines[1] - assert lines[2] == "['field_a']" + assert lines is not None + assert lines[2].startswith("Matching attributes:") + assert "Omitting" not in lines[2] + assert lines[3] == "['field_a']" def test_attrs_with_attribute_comparison_off(self): @attr.s - class SimpleDataObject(object): + class SimpleDataObject: field_a = attr.ib() - field_b = attr.ib(**{ATTRS_EQ_FIELD: False}) + field_b = attr.ib(**{ATTRS_EQ_FIELD: False}) # type: ignore left = SimpleDataObject(1, "b") right = SimpleDataObject(1, "b") lines = callequal(left, right, verbose=2) - assert lines[1].startswith("Matching attributes:") + print(lines) + assert lines is not None + assert lines[2].startswith("Matching attributes:") assert "Omitting" not in lines[1] - assert lines[2] == "['field_a']" - for line in lines[2:]: + assert lines[3] == "['field_a']" + for line in lines[3:]: assert "field_b" not in line def test_comparing_two_different_attrs_classes(self): @attr.s - class SimpleDataObjectOne(object): + class SimpleDataObjectOne: field_a = attr.ib() field_b = attr.ib() @attr.s - class SimpleDataObjectTwo(object): + class SimpleDataObjectTwo: field_a = attr.ib() field_b = attr.ib() @@ -719,7 +977,7 @@ class TestAssert_reprcompare_attrsclass(object): assert lines is None -class TestFormatExplanation(object): +class TestFormatExplanation: def test_special_chars_full(self, testdir): # Issue 453, for the bug this would raise IndexError testdir.makepyfile( @@ -806,16 +1064,13 @@ class TestFormatExplanation(object): assert util.format_explanation(expl) == res -class TestTruncateExplanation(object): - - """ Confirm assertion output is truncated as expected """ - +class TestTruncateExplanation: # The number of lines in the truncation explanation message. Used # to calculate that results have the expected length. LINES_IN_TRUNCATION_MSG = 2 - def test_doesnt_truncate_when_input_is_empty_list(self): - expl = [] + def test_doesnt_truncate_when_input_is_empty_list(self) -> None: + expl = [] # type: List[str] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) assert result == expl @@ -875,7 +1130,7 @@ class TestTruncateExplanation(object): assert last_line_before_trunc_msg.endswith("...") def test_full_output_truncated(self, monkeypatch, testdir): - """ Test against full runpytest() output. """ + """Test against full runpytest() output.""" line_count = 7 line_len = 100 @@ -897,9 +1152,9 @@ class TestTruncateExplanation(object): # without -vv, truncate the message showing a few diff lines only result.stdout.fnmatch_lines( [ - "*- 1*", - "*- 3*", - "*- 5*", + "*+ 1*", + "*+ 3*", + "*+ 5*", "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines, ] ) @@ -940,21 +1195,22 @@ def test_rewritten(testdir): assert testdir.runpytest().ret == 0 -def test_reprcompare_notin(): - config = mock_config() - detail = plugin.pytest_assertrepr_compare(config, "not in", "foo", "aaafoobbb")[1:] - assert detail == ["'foo' is contained here:", " aaafoobbb", "? +++"] +def test_reprcompare_notin() -> None: + assert callop("not in", "foo", "aaafoobbb") == [ + "'foo' not in 'aaafoobbb'", + "'foo' is contained here:", + " aaafoobbb", + "? +++", + ] def test_reprcompare_whitespaces(): - config = mock_config() - detail = plugin.pytest_assertrepr_compare(config, "==", "\r\n", "\n") - assert detail == [ + assert callequal("\r\n", "\n") == [ r"'\r\n' == '\n'", r"Strings contain only whitespace, escaping them using repr()", - r"- '\r\n'", - r"? --", - r"+ '\n'", + r"- '\n'", + r"+ '\r\n'", + r"? ++", ] @@ -970,7 +1226,13 @@ def test_pytest_assertrepr_compare_integration(testdir): ) result = testdir.runpytest() result.stdout.fnmatch_lines( - ["*def test_hello():*", "*assert x == y*", "*E*Extra items*left*", "*E*50*"] + [ + "*def test_hello():*", + "*assert x == y*", + "*E*Extra items*left*", + "*E*50*", + "*= 1 failed in*", + ] ) @@ -1032,7 +1294,7 @@ def test_assertion_options(testdir): result = testdir.runpytest() assert "3 == 4" in result.stdout.str() result = testdir.runpytest_subprocess("--assert=plain") - assert "3 == 4" not in result.stdout.str() + result.stdout.no_fnmatch_line("*3 == 4*") def test_triple_quoted_string_issue113(testdir): @@ -1044,7 +1306,7 @@ def test_triple_quoted_string_issue113(testdir): ) result = testdir.runpytest("--fulltrace") result.stdout.fnmatch_lines(["*1 failed*"]) - assert "SyntaxError" not in result.stdout.str() + result.stdout.no_fnmatch_line("*SyntaxError*") def test_traceback_failure(testdir): @@ -1102,14 +1364,8 @@ def test_traceback_failure(testdir): ) -@pytest.mark.skipif( - sys.version_info[:2] <= (3, 3), - reason="Python 3.4+ shows chained exceptions on multiprocess", -) def test_exception_handling_no_traceback(testdir): - """ - Handle chain exceptions in tasks submitted by the multiprocess module (#1984). - """ + """Handle chain exceptions in tasks submitted by the multiprocess module (#1984).""" p1 = testdir.makepyfile( """ from multiprocessing import Pool @@ -1126,6 +1382,7 @@ def test_exception_handling_no_traceback(testdir): multitask_job() """ ) + testdir.syspathinsert() result = testdir.runpytest(p1, "--tb=long") result.stdout.fnmatch_lines( [ @@ -1139,15 +1396,36 @@ def test_exception_handling_no_traceback(testdir): ) -@pytest.mark.skipif( - "'__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" +@pytest.mark.skipif("'__pypy__' in sys.builtin_module_names") +@pytest.mark.parametrize( + "cmdline_args, warning_output", + [ + ( + ["-OO", "-m", "pytest", "-h"], + ["warning :*PytestConfigWarning:*assert statements are not executed*"], + ), + ( + ["-OO", "-m", "pytest"], + [ + "=*= warnings summary =*=", + "*PytestConfigWarning:*assert statements are not executed*", + ], + ), + ( + ["-OO", "-m", "pytest", "--assert=plain"], + [ + "=*= warnings summary =*=", + "*PytestConfigWarning: ASSERTIONS ARE NOT EXECUTED and FAILING TESTS WILL PASS. " + "Are you using python -O?", + ], + ), + ], ) -def test_warn_missing(testdir): +def test_warn_missing(testdir, cmdline_args, warning_output): testdir.makepyfile("") - result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h") - result.stderr.fnmatch_lines(["*WARNING*assert statements are not executed*"]) - result = testdir.run(sys.executable, "-OO", "-m", "pytest") - result.stderr.fnmatch_lines(["*WARNING*assert statements are not executed*"]) + + result = testdir.run(sys.executable, *cmdline_args) + result.stdout.fnmatch_lines(warning_output) def test_recursion_source_decode(testdir): @@ -1189,46 +1467,7 @@ def test_AssertionError_message(testdir): ) -@pytest.mark.skipif(PY3, reason="This bug does not exist on PY3") -def test_set_with_unsortable_elements(): - # issue #718 - class UnsortableKey(object): - def __init__(self, name): - self.name = name - - def __lt__(self, other): - raise RuntimeError() - - def __repr__(self): - return "repr({})".format(self.name) - - def __eq__(self, other): - return self.name == other.name - - def __hash__(self): - return hash(self.name) - - left_set = {UnsortableKey(str(i)) for i in range(1, 3)} - right_set = {UnsortableKey(str(i)) for i in range(2, 4)} - expl = callequal(left_set, right_set, verbose=True) - # skip first line because it contains the "construction" of the set, which does not have a guaranteed order - expl = expl[1:] - dedent = textwrap.dedent( - """ - Extra items in the left set: - repr(1) - Extra items in the right set: - repr(3) - Full diff (fallback to calling repr on each item): - - repr(1) - repr(2) - + repr(3) - """ - ).strip() - assert "\n".join(expl) == dedent - - -def test_diff_newline_at_end(monkeypatch, testdir): +def test_diff_newline_at_end(testdir): testdir.makepyfile( r""" def test_diff(): @@ -1241,8 +1480,8 @@ def test_diff_newline_at_end(monkeypatch, testdir): r""" *assert 'asdf' == 'asdf\n' * - asdf + * ? - * + asdf - * ? + """ ) @@ -1278,18 +1517,17 @@ def test_assert_indirect_tuple_no_warning(testdir): assert tpl """ ) - result = testdir.runpytest("-rw") + result = testdir.runpytest() output = "\n".join(result.stdout.lines) assert "WR1" not in output -def test_assert_with_unicode(monkeypatch, testdir): +def test_assert_with_unicode(testdir): testdir.makepyfile( - u""" - # -*- coding: utf-8 -*- + """\ def test_unicode(): - assert u'유니코드' == u'Unicode' - """ + assert '유니코드' == 'Unicode' + """ ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*AssertionError*"]) @@ -1310,7 +1548,7 @@ def test_raise_unprintable_assertion_error(testdir): def test_raise_assertion_error_raisin_repr(testdir): testdir.makepyfile( - u""" + """ class RaisingRepr(object): def __repr__(self): raise Exception() @@ -1349,3 +1587,23 @@ def test_exit_from_assertrepr_compare(monkeypatch): with pytest.raises(outcomes.Exit, match="Quitting debugger"): callequal(1, 1) + + +def test_assertion_location_with_coverage(testdir): + """This used to report the wrong location when run with coverage (#5754).""" + p = testdir.makepyfile( + """ + def test(): + assert False, 1 + assert False, 2 + """ + ) + result = testdir.runpytest(str(p)) + result.stdout.fnmatch_lines( + [ + "> assert False, 1", + "E AssertionError: 1", + "E assert False", + "*= 1 failed in*", + ] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py index 87dada213d6..ad3089a23c0 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_assertrewrite.py @@ -1,74 +1,71 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import ast +import errno import glob +import importlib import os import py_compile import stat import sys import textwrap import zipfile +from functools import partial +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional +from typing import Set import py -import six import _pytest._code import pytest from _pytest.assertion import util +from _pytest.assertion.rewrite import _get_assertion_exprs from _pytest.assertion.rewrite import AssertionRewritingHook +from _pytest.assertion.rewrite import get_cache_dir +from _pytest.assertion.rewrite import PYC_TAIL from _pytest.assertion.rewrite import PYTEST_TAG from _pytest.assertion.rewrite import rewrite_asserts -from _pytest.main import EXIT_NOTESTSCOLLECTED - -ast = pytest.importorskip("ast") -if sys.platform.startswith("java"): - # XXX should be xfail - pytest.skip("assert rewrite does currently not work on jython") - - -def setup_module(mod): - mod._old_reprcompare = util._reprcompare - _pytest._code._reprcompare = None - +from _pytest.config import ExitCode +from _pytest.pathlib import make_numbered_dir +from _pytest.pathlib import Path +from _pytest.pytester import Testdir -def teardown_module(mod): - util._reprcompare = mod._old_reprcompare - del mod._old_reprcompare - -def rewrite(src): +def rewrite(src: str) -> ast.Module: tree = ast.parse(src) - rewrite_asserts(tree) + rewrite_asserts(tree, src.encode()) return tree -def getmsg(f, extra_ns=None, must_pass=False): +def getmsg( + f, extra_ns: Optional[Mapping[str, object]] = None, *, must_pass: bool = False +) -> Optional[str]: """Rewrite the assertions in f, run it, and get the failure message.""" src = "\n".join(_pytest._code.Code(f).source().lines) mod = rewrite(src) code = compile(mod, "<test>", "exec") - ns = {} + ns = {} # type: Dict[str, object] if extra_ns is not None: ns.update(extra_ns) exec(code, ns) func = ns[f.__name__] try: - func() + func() # type: ignore[operator] except AssertionError: if must_pass: pytest.fail("shouldn't have raised") - s = six.text_type(sys.exc_info()[1]) + s = str(sys.exc_info()[1]) if not s.startswith("assert"): return "AssertionError: " + s return s else: if not must_pass: pytest.fail("function didn't raise at all") + return None -class TestAssertionRewrite(object): +class TestAssertionRewrite: def test_place_initial_imports(self): s = """'Doc string'\nother = stuff""" m = rewrite(s) @@ -111,10 +108,11 @@ class TestAssertionRewrite(object): assert imp.col_offset == 0 assert isinstance(m.body[3], ast.Expr) - def test_dont_rewrite(self): + def test_dont_rewrite(self) -> None: s = """'PYTEST_DONT_REWRITE'\nassert 14""" m = rewrite(s) assert len(m.body) == 2 + assert isinstance(m.body[1], ast.Assert) assert m.body[1].msg is None def test_dont_rewrite_plugin(self, testdir): @@ -125,84 +123,138 @@ class TestAssertionRewrite(object): } testdir.makepyfile(**contents) result = testdir.runpytest_subprocess() - assert "warnings" not in "".join(result.outlines) + assert "warning" not in "".join(result.outlines) + + def test_rewrites_plugin_as_a_package(self, testdir): + pkgdir = testdir.mkpydir("plugin") + pkgdir.join("__init__.py").write( + "import pytest\n" + "@pytest.fixture\n" + "def special_asserter():\n" + " def special_assert(x, y):\n" + " assert x == y\n" + " return special_assert\n" + ) + testdir.makeconftest('pytest_plugins = ["plugin"]') + testdir.makepyfile("def test(special_asserter): special_asserter(1, 2)\n") + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*assert 1 == 2*"]) + + def test_honors_pep_235(self, testdir, monkeypatch): + # note: couldn't make it fail on macos with a single `sys.path` entry + # note: these modules are named `test_*` to trigger rewriting + testdir.tmpdir.join("test_y.py").write("x = 1") + xdir = testdir.tmpdir.join("x").ensure_dir() + xdir.join("test_Y").ensure_dir().join("__init__.py").write("x = 2") + testdir.makepyfile( + "import test_y\n" + "import test_Y\n" + "def test():\n" + " assert test_y.x == 1\n" + " assert test_Y.x == 2\n" + ) + monkeypatch.syspath_prepend(xdir) + testdir.runpytest().assert_outcomes(passed=1) - def test_name(self, request): - def f(): + def test_name(self, request) -> None: + def f1() -> None: assert False - assert getmsg(f) == "assert False" + assert getmsg(f1) == "assert False" - def f(): + def f2() -> None: f = False assert f - assert getmsg(f) == "assert False" + assert getmsg(f2) == "assert False" - def f(): - assert a_global # noqa + def f3() -> None: + assert a_global # type: ignore[name-defined] # noqa - assert getmsg(f, {"a_global": False}) == "assert False" + assert getmsg(f3, {"a_global": False}) == "assert False" - def f(): - assert sys == 42 + def f4() -> None: + assert sys == 42 # type: ignore[comparison-overlap] verbose = request.config.getoption("verbose") - msg = getmsg(f, {"sys": sys}) + msg = getmsg(f4, {"sys": sys}) if verbose > 0: assert msg == ( "assert <module 'sys' (built-in)> == 42\n" - " -<module 'sys' (built-in)>\n" - " +42" + " +<module 'sys' (built-in)>\n" + " -42" ) else: assert msg == "assert sys == 42" - def f(): - assert cls == 42 # noqa: F821 + def f5() -> None: + assert cls == 42 # type: ignore[name-defined] # noqa: F821 - class X(object): + class X: pass - msg = getmsg(f, {"cls": X}).splitlines() - if verbose > 0: - if six.PY2: - assert msg == [ - "assert <class 'test_assertrewrite.X'> == 42", - " -<class 'test_assertrewrite.X'>", - " +42", - ] - else: - assert msg == [ - "assert <class 'test_...e.<locals>.X'> == 42", - " -<class 'test_assertrewrite.TestAssertionRewrite.test_name.<locals>.X'>", - " +42", - ] + msg = getmsg(f5, {"cls": X}) + assert msg is not None + lines = msg.splitlines() + if verbose > 1: + assert lines == [ + "assert {!r} == 42".format(X), + " +{!r}".format(X), + " -42", + ] + elif verbose > 0: + assert lines == [ + "assert <class 'test_...e.<locals>.X'> == 42", + " +{!r}".format(X), + " -42", + ] else: - assert msg == ["assert cls == 42"] + assert lines == ["assert cls == 42"] + + def test_assertrepr_compare_same_width(self, request) -> None: + """Should use same width/truncation with same initial width.""" - def test_dont_rewrite_if_hasattr_fails(self, request): - class Y(object): - """ A class whos getattr fails, but not with `AttributeError` """ + def f() -> None: + assert "1234567890" * 5 + "A" == "1234567890" * 5 + "B" + + msg = getmsg(f) + assert msg is not None + line = msg.splitlines()[0] + if request.config.getoption("verbose") > 1: + assert line == ( + "assert '12345678901234567890123456789012345678901234567890A' " + "== '12345678901234567890123456789012345678901234567890B'" + ) + else: + assert line == ( + "assert '123456789012...901234567890A' " + "== '123456789012...901234567890B'" + ) + + def test_dont_rewrite_if_hasattr_fails(self, request) -> None: + class Y: + """A class whose getattr fails, but not with `AttributeError`.""" def __getattr__(self, attribute_name): raise KeyError() - def __repr__(self): + def __repr__(self) -> str: return "Y" - def __init__(self): + def __init__(self) -> None: self.foo = 3 - def f(): - assert cls().foo == 2 # noqa + def f() -> None: + assert cls().foo == 2 # type: ignore[name-defined] # noqa: F821 # XXX: looks like the "where" should also be there in verbose mode?! - message = getmsg(f, {"cls": Y}).splitlines() + msg = getmsg(f, {"cls": Y}) + assert msg is not None + lines = msg.splitlines() if request.config.getoption("verbose") > 0: - assert message == ["assert 3 == 2", " -3", " +2"] + assert lines == ["assert 3 == 2", " +3", " -2"] else: - assert message == [ + assert lines == [ "assert 3 == 2", " + where 3 = Y.foo", " + where Y = cls()", @@ -277,156 +329,152 @@ class TestAssertionRewrite(object): ["*AssertionError: To be escaped: %", "*assert 1 == 2"] ) - @pytest.mark.skipif( - sys.version_info < (3,), reason="bytes is a string type in python 2" - ) def test_assertion_messages_bytes(self, testdir): testdir.makepyfile("def test_bytes_assertion():\n assert False, b'ohai!'\n") result = testdir.runpytest() assert result.ret == 1 result.stdout.fnmatch_lines(["*AssertionError: b'ohai!'", "*assert False"]) - def test_boolop(self): - def f(): + def test_boolop(self) -> None: + def f1() -> None: f = g = False assert f and g - assert getmsg(f) == "assert (False)" + assert getmsg(f1) == "assert (False)" - def f(): + def f2() -> None: f = True g = False assert f and g - assert getmsg(f) == "assert (True and False)" + assert getmsg(f2) == "assert (True and False)" - def f(): + def f3() -> None: f = False g = True assert f and g - assert getmsg(f) == "assert (False)" + assert getmsg(f3) == "assert (False)" - def f(): + def f4() -> None: f = g = False assert f or g - assert getmsg(f) == "assert (False or False)" + assert getmsg(f4) == "assert (False or False)" - def f(): + def f5() -> None: f = g = False assert not f and not g - getmsg(f, must_pass=True) + getmsg(f5, must_pass=True) - def x(): + def x() -> bool: return False - def f(): + def f6() -> None: assert x() and x() assert ( - getmsg(f, {"x": x}) + getmsg(f6, {"x": x}) == """assert (False) + where False = x()""" ) - def f(): - assert False or x() + def f7() -> None: + assert False or x() # type: ignore[unreachable] assert ( - getmsg(f, {"x": x}) + getmsg(f7, {"x": x}) == """assert (False or False) + where False = x()""" ) - def f(): + def f8() -> None: assert 1 in {} and 2 in {} - assert getmsg(f) == "assert (1 in {})" + assert getmsg(f8) == "assert (1 in {})" - def f(): + def f9() -> None: x = 1 y = 2 assert x in {1: None} and y in {} - assert getmsg(f) == "assert (1 in {1: None} and 2 in {})" + assert getmsg(f9) == "assert (1 in {1: None} and 2 in {})" - def f(): + def f10() -> None: f = True g = False assert f or g - getmsg(f, must_pass=True) + getmsg(f10, must_pass=True) - def f(): + def f11() -> None: f = g = h = lambda: True assert f() and g() and h() - getmsg(f, must_pass=True) + getmsg(f11, must_pass=True) - def test_short_circuit_evaluation(self): - def f(): - assert True or explode # noqa + def test_short_circuit_evaluation(self) -> None: + def f1() -> None: + assert True or explode # type: ignore[name-defined,unreachable] # noqa: F821 - getmsg(f, must_pass=True) + getmsg(f1, must_pass=True) - def f(): + def f2() -> None: x = 1 assert x == 1 or x == 2 - getmsg(f, must_pass=True) + getmsg(f2, must_pass=True) - def test_unary_op(self): - def f(): + def test_unary_op(self) -> None: + def f1() -> None: x = True assert not x - assert getmsg(f) == "assert not True" + assert getmsg(f1) == "assert not True" - def f(): + def f2() -> None: x = 0 assert ~x + 1 - assert getmsg(f) == "assert (~0 + 1)" + assert getmsg(f2) == "assert (~0 + 1)" - def f(): + def f3() -> None: x = 3 assert -x + x - assert getmsg(f) == "assert (-3 + 3)" + assert getmsg(f3) == "assert (-3 + 3)" - def f(): + def f4() -> None: x = 0 assert +x + x - assert getmsg(f) == "assert (+0 + 0)" + assert getmsg(f4) == "assert (+0 + 0)" - def test_binary_op(self): - def f(): + def test_binary_op(self) -> None: + def f1() -> None: x = 1 y = -1 assert x + y - assert getmsg(f) == "assert (1 + -1)" + assert getmsg(f1) == "assert (1 + -1)" - def f(): + def f2() -> None: assert not 5 % 4 - assert getmsg(f) == "assert not (5 % 4)" + assert getmsg(f2) == "assert not (5 % 4)" - def test_boolop_percent(self): - def f(): + def test_boolop_percent(self) -> None: + def f1() -> None: assert 3 % 2 and False - assert getmsg(f) == "assert ((3 % 2) and False)" + assert getmsg(f1) == "assert ((3 % 2) and False)" - def f(): - assert False or 4 % 2 + def f2() -> None: + assert False or 4 % 2 # type: ignore[unreachable] - assert getmsg(f) == "assert (False or (4 % 2))" + assert getmsg(f2) == "assert (False or (4 % 2))" - @pytest.mark.skipif("sys.version_info < (3,5)") def test_at_operator_issue1290(self, testdir): testdir.makepyfile( """ @@ -441,7 +489,6 @@ class TestAssertionRewrite(object): ) testdir.runpytest().assert_outcomes(passed=1) - @pytest.mark.skipif("sys.version_info < (3,5)") def test_starred_with_side_effect(self, testdir): """See #4412""" testdir.makepyfile( @@ -454,133 +501,133 @@ class TestAssertionRewrite(object): ) testdir.runpytest().assert_outcomes(passed=1) - def test_call(self): - def g(a=42, *args, **kwargs): + def test_call(self) -> None: + def g(a=42, *args, **kwargs) -> bool: return False ns = {"g": g} - def f(): + def f1() -> None: assert g() assert ( - getmsg(f, ns) + getmsg(f1, ns) == """assert False + where False = g()""" ) - def f(): + def f2() -> None: assert g(1) assert ( - getmsg(f, ns) + getmsg(f2, ns) == """assert False + where False = g(1)""" ) - def f(): + def f3() -> None: assert g(1, 2) assert ( - getmsg(f, ns) + getmsg(f3, ns) == """assert False + where False = g(1, 2)""" ) - def f(): + def f4() -> None: assert g(1, g=42) assert ( - getmsg(f, ns) + getmsg(f4, ns) == """assert False + where False = g(1, g=42)""" ) - def f(): + def f5() -> None: assert g(1, 3, g=23) assert ( - getmsg(f, ns) + getmsg(f5, ns) == """assert False + where False = g(1, 3, g=23)""" ) - def f(): + def f6() -> None: seq = [1, 2, 3] assert g(*seq) assert ( - getmsg(f, ns) + getmsg(f6, ns) == """assert False + where False = g(*[1, 2, 3])""" ) - def f(): + def f7() -> None: x = "a" assert g(**{x: 2}) assert ( - getmsg(f, ns) + getmsg(f7, ns) == """assert False + where False = g(**{'a': 2})""" ) - def test_attribute(self): - class X(object): + def test_attribute(self) -> None: + class X: g = 3 ns = {"x": X} - def f(): - assert not x.g # noqa + def f1() -> None: + assert not x.g # type: ignore[name-defined] # noqa: F821 assert ( - getmsg(f, ns) + getmsg(f1, ns) == """assert not 3 + where 3 = x.g""" ) - def f(): - x.a = False # noqa - assert x.a # noqa + def f2() -> None: + x.a = False # type: ignore[name-defined] # noqa: F821 + assert x.a # type: ignore[name-defined] # noqa: F821 assert ( - getmsg(f, ns) + getmsg(f2, ns) == """assert False + where False = x.a""" ) - def test_comparisons(self): - def f(): + def test_comparisons(self) -> None: + def f1() -> None: a, b = range(2) assert b < a - assert getmsg(f) == """assert 1 < 0""" + assert getmsg(f1) == """assert 1 < 0""" - def f(): + def f2() -> None: a, b, c = range(3) assert a > b > c - assert getmsg(f) == """assert 0 > 1""" + assert getmsg(f2) == """assert 0 > 1""" - def f(): + def f3() -> None: a, b, c = range(3) assert a < b > c - assert getmsg(f) == """assert 1 > 2""" + assert getmsg(f3) == """assert 1 > 2""" - def f(): + def f4() -> None: a, b, c = range(3) assert a < b <= c - getmsg(f, must_pass=True) + getmsg(f4, must_pass=True) - def f(): + def f5() -> None: a, b, c = range(3) assert a < b assert b < c - getmsg(f, must_pass=True) + getmsg(f5, must_pass=True) def test_len(self, request): def f(): @@ -589,35 +636,35 @@ class TestAssertionRewrite(object): msg = getmsg(f) if request.config.getoption("verbose") > 0: - assert msg == "assert 10 == 11\n -10\n +11" + assert msg == "assert 10 == 11\n +10\n -11" else: assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])" - def test_custom_reprcompare(self, monkeypatch): - def my_reprcompare(op, left, right): + def test_custom_reprcompare(self, monkeypatch) -> None: + def my_reprcompare1(op, left, right) -> str: return "42" - monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + monkeypatch.setattr(util, "_reprcompare", my_reprcompare1) - def f(): + def f1() -> None: assert 42 < 3 - assert getmsg(f) == "assert 42" + assert getmsg(f1) == "assert 42" - def my_reprcompare(op, left, right): + def my_reprcompare2(op, left, right) -> str: return "{} {} {}".format(left, op, right) - monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + monkeypatch.setattr(util, "_reprcompare", my_reprcompare2) - def f(): + def f2() -> None: assert 1 < 3 < 5 <= 4 < 7 - assert getmsg(f) == "assert 5 <= 4" + assert getmsg(f2) == "assert 5 <= 4" - def test_assert_raising_nonzero_in_comparison(self): - def f(): - class A(object): - def __nonzero__(self): + def test_assert_raising__bool__in_comparison(self) -> None: + def f() -> None: + class A: + def __bool__(self): raise ValueError(42) def __lt__(self, other): @@ -626,22 +673,26 @@ class TestAssertionRewrite(object): def __repr__(self): return "<MY42 object>" - def myany(x): + def myany(x) -> bool: return False assert myany(A() < 0) - assert "<MY42 object> < 0" in getmsg(f) + msg = getmsg(f) + assert msg is not None + assert "<MY42 object> < 0" in msg - def test_formatchar(self): - def f(): - assert "%test" == "test" + def test_formatchar(self) -> None: + def f() -> None: + assert "%test" == "test" # type: ignore[comparison-overlap] - assert getmsg(f).startswith("assert '%test' == 'test'") + msg = getmsg(f) + assert msg is not None + assert msg.startswith("assert '%test' == 'test'") - def test_custom_repr(self, request): - def f(): - class Foo(object): + def test_custom_repr(self, request) -> None: + def f() -> None: + class Foo: a = 1 def __repr__(self): @@ -650,16 +701,18 @@ class TestAssertionRewrite(object): f = Foo() assert 0 == f.a - lines = util._format_lines([getmsg(f)]) + msg = getmsg(f) + assert msg is not None + lines = util._format_lines([msg]) if request.config.getoption("verbose") > 0: - assert lines == ["assert 0 == 1\n -0\n +1"] + assert lines == ["assert 0 == 1\n +0\n -1"] else: assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"] - def test_custom_repr_non_ascii(self): - def f(): - class A(object): - name = u"ä" + def test_custom_repr_non_ascii(self) -> None: + def f() -> None: + class A: + name = "ä" def __repr__(self): return self.name.encode("UTF-8") # only legal in python2 @@ -668,11 +721,12 @@ class TestAssertionRewrite(object): assert not a.name msg = getmsg(f) + assert msg is not None assert "UnicodeDecodeError" not in msg assert "UnicodeEncodeError" not in msg -class TestRewriteOnImport(object): +class TestRewriteOnImport: def test_pycache_is_a_file(self, testdir): testdir.tmpdir.join("__pycache__").write("Hello") testdir.makepyfile( @@ -713,7 +767,7 @@ class TestRewriteOnImport(object): import test_gum.test_lizard""" % (z_fn,) ) - assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED + assert testdir.runpytest().ret == ExitCode.NO_TESTS_COLLECTED def test_readonly(self, testdir): sub = testdir.mkdir("testing") @@ -744,9 +798,6 @@ def test_rewritten(): assert testdir.runpytest_subprocess().ret == 0 def test_orphaned_pyc_file(self, testdir): - if sys.version_info < (3, 0) and hasattr(sys, "pypy_version_info"): - pytest.skip("pypy2 doesn't run orphaned pyc files") - testdir.makepyfile( """ import orphan @@ -772,6 +823,24 @@ def test_rewritten(): assert testdir.runpytest().ret == 0 + def test_cached_pyc_includes_pytest_version(self, testdir, monkeypatch): + """Avoid stale caches (#1671)""" + monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) + testdir.makepyfile( + test_foo=""" + def test_foo(): + assert True + """ + ) + result = testdir.runpytest_subprocess() + assert result.ret == 0 + found_names = glob.glob( + "__pycache__/*-pytest-{}.pyc".format(pytest.__version__) + ) + assert found_names, "pyc with expected tag not found in names: {}".format( + glob.glob("__pycache__/*.pyc") + ) + @pytest.mark.skipif('"__pypy__" in sys.modules') def test_pyc_vs_pyo(self, testdir, monkeypatch): testdir.makepyfile( @@ -781,9 +850,7 @@ def test_rewritten(): "hello" assert test_optimized.__doc__ is None""" ) - p = py.path.local.make_numbered_dir( - prefix="runpytest-", keep=None, rootdir=testdir.tmpdir - ) + p = make_numbered_dir(root=Path(testdir.tmpdir), prefix="runpytest-") tmp = "--basetemp=%s" % p monkeypatch.setenv("PYTHONOPTIMIZE", "2") monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) @@ -812,15 +879,11 @@ def test_rewritten(): testdir.tmpdir.join("test_newlines.py").write(b, "wb") assert testdir.runpytest().ret == 0 - @pytest.mark.skipif( - sys.version_info < (3, 4), - reason="packages without __init__.py not supported on python 2", - ) def test_package_without__init__py(self, testdir): pkg = testdir.mkdir("a_package_without_init_py") pkg.join("module.py").ensure() testdir.makepyfile("import a_package_without_init_py.module") - assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED + assert testdir.runpytest().ret == ExitCode.NO_TESTS_COLLECTED def test_rewrite_warning(self, testdir): testdir.makeconftest( @@ -848,10 +911,8 @@ def test_rewritten(): assert testdir.runpytest_subprocess().ret == 0 def test_remember_rewritten_modules(self, pytestconfig, testdir, monkeypatch): - """ - AssertionRewriteHook should remember rewritten modules so it - doesn't give false positives (#2005). - """ + """`AssertionRewriteHook` should remember rewritten modules so it + doesn't give false positives (#2005).""" monkeypatch.syspath_prepend(testdir.tmpdir) testdir.makepyfile(test_remember_rewritten_modules="") warnings = [] @@ -859,8 +920,10 @@ def test_rewritten(): monkeypatch.setattr( hook, "_warn_already_imported", lambda code, msg: warnings.append(msg) ) - hook.find_module("test_remember_rewritten_modules") - hook.load_module("test_remember_rewritten_modules") + spec = hook.find_spec("test_remember_rewritten_modules") + assert spec is not None + module = importlib.util.module_from_spec(spec) + hook.exec_module(module) hook.mark_rewrite("test_remember_rewritten_modules") hook.mark_rewrite("test_remember_rewritten_modules") assert warnings == [] @@ -878,7 +941,7 @@ def test_rewritten(): testdir.chdir() result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines(["*= 1 passed in *=*"]) - assert "pytest-warning summary" not in result.stdout.str() + result.stdout.no_fnmatch_line("*pytest-warning summary*") def test_rewrite_warning_using_pytest_plugins_env_var(self, testdir, monkeypatch): monkeypatch.setenv("PYTEST_PLUGINS", "plugin") @@ -896,99 +959,10 @@ def test_rewritten(): testdir.chdir() result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines(["*= 1 passed in *=*"]) - assert "pytest-warning summary" not in result.stdout.str() - - @pytest.mark.skipif(sys.version_info[0] > 2, reason="python 2 only") - def test_rewrite_future_imports(self, testdir): - """Test that rewritten modules don't inherit the __future__ flags - from the assertrewrite module. - - assertion.rewrite imports __future__.division (and others), so - ensure rewritten modules don't inherit those flags. - - The test below will fail if __future__.division is enabled - """ - testdir.makepyfile( - """ - def test(): - x = 1 / 2 - assert type(x) is int - """ - ) - result = testdir.runpytest() - assert result.ret == 0 - - -class TestAssertionRewriteHookDetails(object): - def test_loader_is_package_false_for_module(self, testdir): - testdir.makepyfile( - test_fun=""" - def test_loader(): - assert not __loader__.is_package(__name__) - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines(["* 1 passed*"]) - - def test_loader_is_package_true_for_package(self, testdir): - testdir.makepyfile( - test_fun=""" - def test_loader(): - assert not __loader__.is_package(__name__) - - def test_fun(): - assert __loader__.is_package('fun') - - def test_missing(): - assert not __loader__.is_package('pytest_not_there') - """ - ) - testdir.mkpydir("fun") - result = testdir.runpytest() - result.stdout.fnmatch_lines(["* 3 passed*"]) - - @pytest.mark.skipif("sys.version_info[0] >= 3") - @pytest.mark.xfail("hasattr(sys, 'pypy_translation_info')") - def test_assume_ascii(self, testdir): - content = "u'\xe2\x99\xa5\x01\xfe'" - testdir.tmpdir.join("test_encoding.py").write(content, "wb") - res = testdir.runpytest() - assert res.ret != 0 - assert "SyntaxError: Non-ASCII character" in res.stdout.str() - - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_detect_coding_cookie(self, testdir): - testdir.makepyfile( - test_cookie=""" - # -*- coding: utf-8 -*- - u"St\xc3\xa4d" - def test_rewritten(): - assert "@py_builtins" in globals()""" - ) - assert testdir.runpytest().ret == 0 + result.stdout.no_fnmatch_line("*pytest-warning summary*") - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_detect_coding_cookie_second_line(self, testdir): - testdir.makepyfile( - test_cookie=""" - # -*- coding: utf-8 -*- - u"St\xc3\xa4d" - def test_rewritten(): - assert "@py_builtins" in globals()""" - ) - assert testdir.runpytest().ret == 0 - - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_detect_coding_cookie_crlf(self, testdir): - testdir.makepyfile( - test_cookie=""" - # -*- coding: utf-8 -*- - u"St\xc3\xa4d" - def test_rewritten(): - assert "@py_builtins" in globals()""" - ) - assert testdir.runpytest().ret == 0 +class TestAssertionRewriteHookDetails: def test_sys_meta_path_munged(self, testdir): testdir.makepyfile( """ @@ -997,27 +971,38 @@ class TestAssertionRewriteHookDetails(object): ) assert testdir.runpytest().ret == 0 - def test_write_pyc(self, testdir, tmpdir, monkeypatch): + def test_write_pyc(self, testdir: Testdir, tmpdir, monkeypatch) -> None: from _pytest.assertion.rewrite import _write_pyc from _pytest.assertion import AssertionState - import atomicwrites - from contextlib import contextmanager - config = testdir.parseconfig([]) + config = testdir.parseconfig() state = AssertionState(config, "rewrite") - source_path = tmpdir.ensure("source.py") + source_path = str(tmpdir.ensure("source.py")) pycpath = tmpdir.join("pyc").strpath - assert _write_pyc(state, [1], source_path.stat(), pycpath) + co = compile("1", "f.py", "single") + assert _write_pyc(state, co, os.stat(source_path), pycpath) + + if sys.platform == "win32": + from contextlib import contextmanager + + @contextmanager + def atomic_write_failed(fn, mode="r", overwrite=False): + e = OSError() + e.errno = 10 + raise e + yield # type:ignore[unreachable] + + monkeypatch.setattr( + _pytest.assertion.rewrite, "atomic_write", atomic_write_failed + ) + else: + + def raise_oserror(*args): + raise OSError() - @contextmanager - def atomic_write_failed(fn, mode="r", overwrite=False): - e = IOError() - e.errno = 10 - raise e - yield + monkeypatch.setattr("os.rename", raise_oserror) - monkeypatch.setattr(atomicwrites, "atomic_write", atomic_write_failed) - assert not _write_pyc(state, [1], source_path.stat(), pycpath) + assert not _write_pyc(state, co, os.stat(source_path), pycpath) def test_resources_provider_for_loader(self, testdir): """ @@ -1049,7 +1034,7 @@ class TestAssertionRewriteHookDetails(object): result = testdir.runpytest_subprocess() result.assert_outcomes(passed=1) - def test_read_pyc(self, tmpdir): + def test_read_pyc(self, tmp_path: Path) -> None: """ Ensure that the `_read_pyc` can properly deal with corrupted pyc files. In those circumstances it should just give up instead of generating @@ -1058,92 +1043,53 @@ class TestAssertionRewriteHookDetails(object): import py_compile from _pytest.assertion.rewrite import _read_pyc - source = tmpdir.join("source.py") - pyc = source + "c" + source = tmp_path / "source.py" + pyc = Path(str(source) + "c") - source.write("def test(): pass") + source.write_text("def test(): pass") py_compile.compile(str(source), str(pyc)) - contents = pyc.read(mode="rb") + contents = pyc.read_bytes() strip_bytes = 20 # header is around 8 bytes, strip a little more assert len(contents) > strip_bytes - pyc.write(contents[:strip_bytes], mode="wb") + pyc.write_bytes(contents[:strip_bytes]) - assert _read_pyc(source, str(pyc)) is None # no error + assert _read_pyc(source, pyc) is None # no error - def test_reload_is_same(self, testdir): - # A file that will be picked up during collecting. - testdir.tmpdir.join("file.py").ensure() - testdir.tmpdir.join("pytest.ini").write( - textwrap.dedent( - """ + def test_reload_is_same_and_reloads(self, testdir: Testdir) -> None: + """Reloading a (collected) module after change picks up the change.""" + testdir.makeini( + """ [pytest] python_files = *.py - """ - ) - ) - - testdir.makepyfile( - test_fun=""" - import sys - try: - from imp import reload - except ImportError: - pass - - def test_loader(): - import file - assert sys.modules["file"] is reload(file) """ ) - result = testdir.runpytest("-s") - result.stdout.fnmatch_lines(["* 1 passed*"]) - - def test_reload_reloads(self, testdir): - """Reloading a module after change picks up the change.""" - testdir.tmpdir.join("file.py").write( - textwrap.dedent( - """ + testdir.makepyfile( + file=""" def reloaded(): return False def rewrite_self(): with open(__file__, 'w') as self: self.write('def reloaded(): return True') - """ - ) - ) - testdir.tmpdir.join("pytest.ini").write( - textwrap.dedent( - """ - [pytest] - python_files = *.py - """ - ) - ) - - testdir.makepyfile( + """, test_fun=""" import sys - try: - from imp import reload - except ImportError: - pass + from importlib import reload def test_loader(): import file assert not file.reloaded() file.rewrite_self() - reload(file) + assert sys.modules["file"] is reload(file) assert file.reloaded() - """ + """, ) - result = testdir.runpytest("-s") + result = testdir.runpytest() result.stdout.fnmatch_lines(["* 1 passed*"]) def test_get_data_support(self, testdir): - """Implement optional PEP302 api (#808). - """ + """Implement optional PEP302 api (#808).""" path = testdir.mkpydir("foo") path.join("test_foo.py").write( textwrap.dedent( @@ -1177,10 +1123,10 @@ def test_issue731(testdir): """ ) result = testdir.runpytest() - assert "unbalanced braces" not in result.stdout.str() + result.stdout.no_fnmatch_line("*unbalanced braces*") -class TestIssue925(object): +class TestIssue925: def test_simple_case(self, testdir): testdir.makepyfile( """ @@ -1258,17 +1204,17 @@ def test_source_mtime_long_long(testdir, offset): assert result.ret == 0 -def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch): +def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch) -> None: """Fix infinite recursion when writing pyc files: if an import happens to be triggered when writing the pyc file, this would cause another call to the hook, which would trigger another pyc writing, which could trigger another import, and so on. (#3506)""" - from _pytest.assertion import rewrite + from _pytest.assertion import rewrite as rewritemod testdir.syspathinsert() testdir.makepyfile(test_foo="def test_foo(): pass") testdir.makepyfile(test_bar="def test_bar(): pass") - original_write_pyc = rewrite._write_pyc + original_write_pyc = rewritemod._write_pyc write_pyc_called = [] @@ -1276,50 +1222,53 @@ def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch): # make a note that we have called _write_pyc write_pyc_called.append(True) # try to import a module at this point: we should not try to rewrite this module - assert hook.find_module("test_bar") is None + assert hook.find_spec("test_bar") is None return original_write_pyc(*args, **kwargs) - monkeypatch.setattr(rewrite, "_write_pyc", spy_write_pyc) + monkeypatch.setattr(rewritemod, "_write_pyc", spy_write_pyc) monkeypatch.setattr(sys, "dont_write_bytecode", False) hook = AssertionRewritingHook(pytestconfig) - assert hook.find_module("test_foo") is not None + spec = hook.find_spec("test_foo") + assert spec is not None + module = importlib.util.module_from_spec(spec) + hook.exec_module(module) assert len(write_pyc_called) == 1 -class TestEarlyRewriteBailout(object): +class TestEarlyRewriteBailout: @pytest.fixture - def hook(self, pytestconfig, monkeypatch, testdir): + def hook(self, pytestconfig, monkeypatch, testdir) -> AssertionRewritingHook: """Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track - if imp.find_module has been called. + if PathFinder.find_spec has been called. """ - import imp + import importlib.machinery - self.find_module_calls = [] - self.initial_paths = set() + self.find_spec_calls = [] # type: List[str] + self.initial_paths = set() # type: Set[py.path.local] - class StubSession(object): + class StubSession: _initialpaths = self.initial_paths def isinitpath(self, p): return p in self._initialpaths - def spy_imp_find_module(name, path): - self.find_module_calls.append(name) - return imp.find_module(name, path) + def spy_find_spec(name, path): + self.find_spec_calls.append(name) + return importlib.machinery.PathFinder.find_spec(name, path) hook = AssertionRewritingHook(pytestconfig) # use default patterns, otherwise we inherit pytest's testing config hook.fnpats[:] = ["test_*.py", "*_test.py"] - monkeypatch.setattr(hook, "_imp_find_module", spy_imp_find_module) - hook.set_session(StubSession()) + monkeypatch.setattr(hook, "_find_spec", spy_find_spec) + hook.set_session(StubSession()) # type: ignore[arg-type] testdir.syspathinsert() return hook - def test_basic(self, testdir, hook): + def test_basic(self, testdir, hook: AssertionRewritingHook) -> None: """ - Ensure we avoid calling imp.find_module when we know for sure a certain module will not be rewritten - to optimize assertion rewriting (#3918). + Ensure we avoid calling PathFinder.find_spec when we know for sure a certain + module will not be rewritten to optimize assertion rewriting (#3918). """ testdir.makeconftest( """ @@ -1334,37 +1283,39 @@ class TestEarlyRewriteBailout(object): self.initial_paths.add(foobar_path) # conftest files should always be rewritten - assert hook.find_module("conftest") is not None - assert self.find_module_calls == ["conftest"] + assert hook.find_spec("conftest") is not None + assert self.find_spec_calls == ["conftest"] # files matching "python_files" mask should always be rewritten - assert hook.find_module("test_foo") is not None - assert self.find_module_calls == ["conftest", "test_foo"] + assert hook.find_spec("test_foo") is not None + assert self.find_spec_calls == ["conftest", "test_foo"] # file does not match "python_files": early bailout - assert hook.find_module("bar") is None - assert self.find_module_calls == ["conftest", "test_foo"] + assert hook.find_spec("bar") is None + assert self.find_spec_calls == ["conftest", "test_foo"] # file is an initial path (passed on the command-line): should be rewritten - assert hook.find_module("foobar") is not None - assert self.find_module_calls == ["conftest", "test_foo", "foobar"] + assert hook.find_spec("foobar") is not None + assert self.find_spec_calls == ["conftest", "test_foo", "foobar"] - def test_pattern_contains_subdirectories(self, testdir, hook): + def test_pattern_contains_subdirectories( + self, testdir, hook: AssertionRewritingHook + ) -> None: """If one of the python_files patterns contain subdirectories ("tests/**.py") we can't bailout early - because we need to match with the full path, which can only be found by calling imp.find_module. + because we need to match with the full path, which can only be found by calling PathFinder.find_spec """ p = testdir.makepyfile( **{ - "tests/file.py": """ - def test_simple_failure(): - assert 1 + 1 == 3 - """ + "tests/file.py": """\ + def test_simple_failure(): + assert 1 + 1 == 3 + """ } ) testdir.syspathinsert(p.dirpath()) hook.fnpats[:] = ["tests/**.py"] - assert hook.find_module("file") is not None - assert self.find_module_calls == ["file"] + assert hook.find_spec("file") is not None + assert self.find_spec_calls == ["file"] @pytest.mark.skipif( sys.platform.startswith("win32"), reason="cannot remove cwd on Windows" @@ -1378,20 +1329,311 @@ class TestEarlyRewriteBailout(object): testdir.makepyfile( **{ - "test_setup_nonexisting_cwd.py": """ - import os - import shutil - import tempfile - - d = tempfile.mkdtemp() - os.chdir(d) - shutil.rmtree(d) - """, - "test_test.py": """ - def test(): + "test_setup_nonexisting_cwd.py": """\ + import os + import shutil + import tempfile + + d = tempfile.mkdtemp() + os.chdir(d) + shutil.rmtree(d) + """, + "test_test.py": """\ + def test(): + pass + """, + } + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["* 1 passed in *"]) + + +class TestAssertionPass: + def test_option_default(self, testdir): + config = testdir.parseconfig() + assert config.getini("enable_assertion_pass_hook") is False + + @pytest.fixture + def flag_on(self, testdir): + testdir.makeini("[pytest]\nenable_assertion_pass_hook = True\n") + + @pytest.fixture + def hook_on(self, testdir): + testdir.makeconftest( + """\ + def pytest_assertion_pass(item, lineno, orig, expl): + raise Exception("Assertion Passed: {} {} at line {}".format(orig, expl, lineno)) + """ + ) + + def test_hook_call(self, testdir, flag_on, hook_on): + testdir.makepyfile( + """\ + def test_simple(): + a=1 + b=2 + c=3 + d=0 + + assert a+b == c+d + + # cover failing assertions with a message + def test_fails(): + assert False, "assert with message" + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + "*Assertion Passed: a+b == c+d (1 + 2) == (3 + 0) at line 7*" + ) + + def test_hook_call_with_parens(self, testdir, flag_on, hook_on): + testdir.makepyfile( + """\ + def f(): return 1 + def test(): + assert f() + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines("*Assertion Passed: f() 1") + + def test_hook_not_called_without_hookimpl(self, testdir, monkeypatch, flag_on): + """Assertion pass should not be called (and hence formatting should + not occur) if there is no hook declared for pytest_assertion_pass""" + + def raise_on_assertionpass(*_, **__): + raise Exception("Assertion passed called when it shouldn't!") + + monkeypatch.setattr( + _pytest.assertion.rewrite, "_call_assertion_pass", raise_on_assertionpass + ) + + testdir.makepyfile( + """\ + def test_simple(): + a=1 + b=2 + c=3 + d=0 + + assert a+b == c+d + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + def test_hook_not_called_without_cmd_option(self, testdir, monkeypatch): + """Assertion pass should not be called (and hence formatting should + not occur) if there is no hook declared for pytest_assertion_pass""" + + def raise_on_assertionpass(*_, **__): + raise Exception("Assertion passed called when it shouldn't!") + + monkeypatch.setattr( + _pytest.assertion.rewrite, "_call_assertion_pass", raise_on_assertionpass + ) + + testdir.makeconftest( + """\ + def pytest_assertion_pass(item, lineno, orig, expl): + raise Exception("Assertion Passed: {} {} at line {}".format(orig, expl, lineno)) + """ + ) + + testdir.makepyfile( + """\ + def test_simple(): + a=1 + b=2 + c=3 + d=0 + + assert a+b == c+d + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + +@pytest.mark.parametrize( + ("src", "expected"), + ( + # fmt: off + pytest.param(b"", {}, id="trivial"), + pytest.param( + b"def x(): assert 1\n", + {1: "1"}, + id="assert statement not on own line", + ), + pytest.param( + b"def x():\n" + b" assert 1\n" + b" assert 1+2\n", + {2: "1", 3: "1+2"}, + id="multiple assertions", + ), + pytest.param( + # changes in encoding cause the byte offsets to be different + "# -*- coding: latin1\n" + "def ÀÀÀÀÀ(): assert 1\n".encode("latin1"), + {2: "1"}, + id="latin1 encoded on first line\n", + ), + pytest.param( + # using the default utf-8 encoding + "def ÀÀÀÀÀ(): assert 1\n".encode(), + {1: "1"}, + id="utf-8 encoded on first line", + ), + pytest.param( + b"def x():\n" + b" assert (\n" + b" 1 + 2 # comment\n" + b" )\n", + {2: "(\n 1 + 2 # comment\n )"}, + id="multi-line assertion", + ), + pytest.param( + b"def x():\n" + b" assert y == [\n" + b" 1, 2, 3\n" + b" ]\n", + {2: "y == [\n 1, 2, 3\n ]"}, + id="multi line assert with list continuation", + ), + pytest.param( + b"def x():\n" + b" assert 1 + \\\n" + b" 2\n", + {2: "1 + \\\n 2"}, + id="backslash continuation", + ), + pytest.param( + b"def x():\n" + b" assert x, y\n", + {2: "x"}, + id="assertion with message", + ), + pytest.param( + b"def x():\n" + b" assert (\n" + b" f(1, 2, 3)\n" + b" ), 'f did not work!'\n", + {2: "(\n f(1, 2, 3)\n )"}, + id="assertion with message, test spanning multiple lines", + ), + pytest.param( + b"def x():\n" + b" assert \\\n" + b" x\\\n" + b" , 'failure message'\n", + {2: "x"}, + id="escaped newlines plus message", + ), + pytest.param( + b"def x(): assert 5", + {1: "5"}, + id="no newline at end of file", + ), + # fmt: on + ), +) +def test_get_assertion_exprs(src, expected): + assert _get_assertion_exprs(src) == expected + + +def test_try_makedirs(monkeypatch, tmp_path: Path) -> None: + from _pytest.assertion.rewrite import try_makedirs + + p = tmp_path / "foo" + + # create + assert try_makedirs(p) + assert p.is_dir() + + # already exist + assert try_makedirs(p) + + # monkeypatch to simulate all error situations + def fake_mkdir(p, exist_ok=False, *, exc): + assert isinstance(p, str) + raise exc + + monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=FileNotFoundError())) + assert not try_makedirs(p) + + monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=NotADirectoryError())) + assert not try_makedirs(p) + + monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=PermissionError())) + assert not try_makedirs(p) + + err = OSError() + err.errno = errno.EROFS + monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) + assert not try_makedirs(p) + + # unhandled OSError should raise + err = OSError() + err.errno = errno.ECHILD + monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) + with pytest.raises(OSError) as exc_info: + try_makedirs(p) + assert exc_info.value.errno == errno.ECHILD + + +class TestPyCacheDir: + @pytest.mark.parametrize( + "prefix, source, expected", + [ + ("c:/tmp/pycs", "d:/projects/src/foo.py", "c:/tmp/pycs/projects/src"), + (None, "d:/projects/src/foo.py", "d:/projects/src/__pycache__"), + ("/tmp/pycs", "/home/projects/src/foo.py", "/tmp/pycs/home/projects/src"), + (None, "/home/projects/src/foo.py", "/home/projects/src/__pycache__"), + ], + ) + def test_get_cache_dir(self, monkeypatch, prefix, source, expected): + if prefix: + if sys.version_info < (3, 8): + pytest.skip("pycache_prefix not available in py<38") + monkeypatch.setattr(sys, "pycache_prefix", prefix) # type:ignore + + assert get_cache_dir(Path(source)) == Path(expected) + + @pytest.mark.skipif( + sys.version_info < (3, 8), reason="pycache_prefix not available in py<38" + ) + def test_sys_pycache_prefix_integration(self, tmp_path, monkeypatch, testdir): + """Integration test for sys.pycache_prefix (#4730).""" + pycache_prefix = tmp_path / "my/pycs" + monkeypatch.setattr(sys, "pycache_prefix", str(pycache_prefix)) + monkeypatch.setattr(sys, "dont_write_bytecode", False) + + testdir.makepyfile( + **{ + "src/test_foo.py": """ + import bar + def test_foo(): pass """, + "src/bar/__init__.py": "", } ) result = testdir.runpytest() - result.stdout.fnmatch_lines(["* 1 passed in *"]) + assert result.ret == 0 + + test_foo = Path(testdir.tmpdir) / "src/test_foo.py" + bar_init = Path(testdir.tmpdir) / "src/bar/__init__.py" + assert test_foo.is_file() + assert bar_init.is_file() + + # test file: rewritten, custom pytest cache tag + test_foo_pyc = get_cache_dir(test_foo) / ("test_foo" + PYC_TAIL) + assert test_foo_pyc.is_file() + + # normal file: not touched by pytest, normal cache tag + bar_init_pyc = get_cache_dir(bar_init) / "__init__.{cache_tag}.pyc".format( + cache_tag=sys.implementation.cache_tag + ) + assert bar_init_pyc.is_file() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py index 6de0b312929..a911257ce24 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_cacheprovider.py @@ -1,22 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import shutil +import stat import sys -import textwrap import py import pytest -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.config import ExitCode +from _pytest.pytester import Testdir pytest_plugins = ("pytester",) -class TestNewAPI(object): +class TestNewAPI: def test_config_cache_makedir(self, testdir): testdir.makeini("[pytest]") config = testdir.parseconfigure() @@ -36,7 +32,7 @@ class TestNewAPI(object): val = config.cache.get("key/name", -2) assert val == -2 - @pytest.mark.filterwarnings("default") + @pytest.mark.filterwarnings("ignore:could not create cache path") def test_cache_writefail_cachfile_silent(self, testdir): testdir.makeini("[pytest]") testdir.tmpdir.join(".pytest_cache").write("gone wrong") @@ -50,26 +46,41 @@ class TestNewAPI(object): ) def test_cache_writefail_permissions(self, testdir): testdir.makeini("[pytest]") + cache_dir = str(testdir.tmpdir.ensure_dir(".pytest_cache")) + mode = os.stat(cache_dir)[stat.ST_MODE] testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0) - config = testdir.parseconfigure() - cache = config.cache - cache.set("test/broken", []) + try: + config = testdir.parseconfigure() + cache = config.cache + cache.set("test/broken", []) + finally: + testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode) @pytest.mark.skipif(sys.platform.startswith("win"), reason="no chmod on windows") @pytest.mark.filterwarnings("default") - def test_cache_failure_warns(self, testdir): + def test_cache_failure_warns(self, testdir, monkeypatch): + monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") + cache_dir = str(testdir.tmpdir.ensure_dir(".pytest_cache")) + mode = os.stat(cache_dir)[stat.ST_MODE] testdir.tmpdir.ensure_dir(".pytest_cache").chmod(0) - testdir.makepyfile( - """ - def test_error(): - raise Exception - - """ - ) - result = testdir.runpytest("-rw") - assert result.ret == 1 - # warnings from nodeids, lastfailed, and stepwise - result.stdout.fnmatch_lines(["*could not create cache path*", "*3 warnings*"]) + try: + testdir.makepyfile("def test_error(): raise Exception") + result = testdir.runpytest() + assert result.ret == 1 + # warnings from nodeids, lastfailed, and stepwise + result.stdout.fnmatch_lines( + [ + # Validate location/stacklevel of warning from cacheprovider. + "*= warnings summary =*", + "*/cacheprovider.py:*", + " */cacheprovider.py:*: PytestCacheWarning: could not create cache path " + "{}/v/cache/nodeids".format(cache_dir), + ' config.cache.set("cache/nodeids", sorted(self.cached_nodeids))', + "*1 failed, 3 warnings in*", + ] + ) + finally: + testdir.tmpdir.ensure_dir(".pytest_cache").chmod(mode) def test_config_cache(self, testdir): testdir.makeconftest( @@ -168,12 +179,7 @@ def test_cache_reportheader_external_abspath(testdir, tmpdir_factory): "test_cache_reportheader_external_abspath_abs" ) - testdir.makepyfile( - """ - def test_hello(): - pass - """ - ) + testdir.makepyfile("def test_hello(): pass") testdir.makeini( """ [pytest] @@ -182,7 +188,6 @@ def test_cache_reportheader_external_abspath(testdir, tmpdir_factory): abscache=external_cache ) ) - result = testdir.runpytest("-v") result.stdout.fnmatch_lines( ["cachedir: {abscache}".format(abscache=external_cache)] @@ -241,38 +246,34 @@ def test_cache_show(testdir): assert result.ret == 0 -class TestLastFailed(object): +class TestLastFailed: def test_lastfailed_usecase(self, testdir, monkeypatch): - monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1") + monkeypatch.setattr("sys.dont_write_bytecode", True) p = testdir.makepyfile( """ - def test_1(): - assert 0 - def test_2(): - assert 0 - def test_3(): - assert 1 - """ + def test_1(): assert 0 + def test_2(): assert 0 + def test_3(): assert 1 + """ ) - result = testdir.runpytest() + result = testdir.runpytest(str(p)) result.stdout.fnmatch_lines(["*2 failed*"]) - p.write( - textwrap.dedent( - """\ - def test_1(): - assert 1 - - def test_2(): - assert 1 - - def test_3(): - assert 0 - """ - ) + p = testdir.makepyfile( + """ + def test_1(): assert 1 + def test_2(): assert 1 + def test_3(): assert 0 + """ ) - result = testdir.runpytest("--lf") - result.stdout.fnmatch_lines(["*2 passed*1 desel*"]) - result = testdir.runpytest("--lf") + result = testdir.runpytest(str(p), "--lf") + result.stdout.fnmatch_lines( + [ + "collected 3 items / 1 deselected / 2 selected", + "run-last-failure: rerun previous 2 failures", + "*= 2 passed, 1 deselected in *", + ] + ) + result = testdir.runpytest(str(p), "--lf") result.stdout.fnmatch_lines( [ "collected 3 items", @@ -280,8 +281,11 @@ class TestLastFailed(object): "*1 failed*2 passed*", ] ) - result = testdir.runpytest("--lf", "--cache-clear") + testdir.tmpdir.join(".pytest_cache").mkdir(".git") + result = testdir.runpytest(str(p), "--lf", "--cache-clear") result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) + assert testdir.tmpdir.join(".pytest_cache", "README.md").isfile() + assert testdir.tmpdir.join(".pytest_cache", ".git").isdir() # Run this again to make sure clear-cache is robust if os.path.isdir(".pytest_cache"): @@ -290,63 +294,45 @@ class TestLastFailed(object): result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) def test_failedfirst_order(self, testdir): - testdir.tmpdir.join("test_a.py").write( - textwrap.dedent( - """\ - def test_always_passes(): - assert 1 - """ - ) - ) - testdir.tmpdir.join("test_b.py").write( - textwrap.dedent( - """\ - def test_always_fails(): - assert 0 - """ - ) + testdir.makepyfile( + test_a="def test_always_passes(): pass", + test_b="def test_always_fails(): assert 0", ) result = testdir.runpytest() # Test order will be collection order; alphabetical result.stdout.fnmatch_lines(["test_a.py*", "test_b.py*"]) result = testdir.runpytest("--ff") - # Test order will be failing tests firs - result.stdout.fnmatch_lines(["test_b.py*", "test_a.py*"]) + # Test order will be failing tests first + result.stdout.fnmatch_lines( + [ + "collected 2 items", + "run-last-failure: rerun previous 1 failure first", + "test_b.py*", + "test_a.py*", + ] + ) def test_lastfailed_failedfirst_order(self, testdir): testdir.makepyfile( - **{ - "test_a.py": """\ - def test_always_passes(): - assert 1 - """, - "test_b.py": """\ - def test_always_fails(): - assert 0 - """, - } + test_a="def test_always_passes(): assert 1", + test_b="def test_always_fails(): assert 0", ) result = testdir.runpytest() # Test order will be collection order; alphabetical result.stdout.fnmatch_lines(["test_a.py*", "test_b.py*"]) result = testdir.runpytest("--lf", "--ff") - # Test order will be failing tests firs + # Test order will be failing tests first result.stdout.fnmatch_lines(["test_b.py*"]) - assert "test_a.py" not in result.stdout.str() + result.stdout.no_fnmatch_line("*test_a.py*") def test_lastfailed_difference_invocations(self, testdir, monkeypatch): - monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1") + monkeypatch.setattr("sys.dont_write_bytecode", True) testdir.makepyfile( - test_a="""\ - def test_a1(): - assert 0 - def test_a2(): - assert 1 - """, - test_b="""\ - def test_b1(): - assert 0 + test_a=""" + def test_a1(): assert 0 + def test_a2(): assert 1 """, + test_b="def test_b1(): assert 0", ) p = testdir.tmpdir.join("test_a.py") p2 = testdir.tmpdir.join("test_b.py") @@ -355,36 +341,25 @@ class TestLastFailed(object): result.stdout.fnmatch_lines(["*2 failed*"]) result = testdir.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 failed*"]) - p2.write( - textwrap.dedent( - """\ - def test_b1(): - assert 1 - """ - ) - ) + + testdir.makepyfile(test_b="def test_b1(): assert 1") result = testdir.runpytest("--lf", p2) result.stdout.fnmatch_lines(["*1 passed*"]) result = testdir.runpytest("--lf", p) - result.stdout.fnmatch_lines(["*1 failed*1 desel*"]) + result.stdout.fnmatch_lines( + [ + "collected 2 items / 1 deselected / 1 selected", + "run-last-failure: rerun previous 1 failure", + "*= 1 failed, 1 deselected in *", + ] + ) def test_lastfailed_usecase_splice(self, testdir, monkeypatch): - monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1") + monkeypatch.setattr("sys.dont_write_bytecode", True) testdir.makepyfile( - """\ - def test_1(): - assert 0 - """ + "def test_1(): assert 0", test_something="def test_2(): assert 0" ) p2 = testdir.tmpdir.join("test_something.py") - p2.write( - textwrap.dedent( - """\ - def test_2(): - assert 0 - """ - ) - ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) result = testdir.runpytest("--lf", p2) @@ -426,18 +401,14 @@ class TestLastFailed(object): def test_terminal_report_lastfailed(self, testdir): test_a = testdir.makepyfile( test_a=""" - def test_a1(): - pass - def test_a2(): - pass + def test_a1(): pass + def test_a2(): pass """ ) test_b = testdir.makepyfile( test_b=""" - def test_b1(): - assert 0 - def test_b2(): - assert 0 + def test_b1(): assert 0 + def test_b2(): assert 0 """ ) result = testdir.runpytest() @@ -482,10 +453,8 @@ class TestLastFailed(object): def test_terminal_report_failedfirst(self, testdir): testdir.makepyfile( test_a=""" - def test_a1(): - assert 0 - def test_a2(): - pass + def test_a1(): assert 0 + def test_a2(): pass """ ) result = testdir.runpytest() @@ -532,7 +501,6 @@ class TestLastFailed(object): assert list(lastfailed) == ["test_maybe.py::test_hello"] def test_lastfailed_failure_subset(self, testdir, monkeypatch): - testdir.makepyfile( test_maybe=""" import os @@ -550,6 +518,7 @@ class TestLastFailed(object): env = os.environ if '1' == env['FAILIMPORT']: raise ImportError('fail') + def test_hello(): assert '0' == env['FAILTEST'] @@ -603,8 +572,7 @@ class TestLastFailed(object): """ import pytest @pytest.mark.xfail - def test(): - assert 0 + def test(): assert 0 """ ) result = testdir.runpytest() @@ -616,8 +584,7 @@ class TestLastFailed(object): """ import pytest @pytest.mark.xfail(strict=True) - def test(): - pass + def test(): pass """ ) result = testdir.runpytest() @@ -631,8 +598,7 @@ class TestLastFailed(object): testdir.makepyfile( """ import pytest - def test(): - assert 0 + def test(): assert 0 """ ) result = testdir.runpytest() @@ -645,8 +611,7 @@ class TestLastFailed(object): """ import pytest @pytest.{mark} - def test(): - assert 0 + def test(): assert 0 """.format( mark=mark ) @@ -665,11 +630,11 @@ class TestLastFailed(object): if quiet: args.append("-q") result = testdir.runpytest(*args) - assert "run all" not in result.stdout.str() + result.stdout.no_fnmatch_line("*run all*") result = testdir.runpytest(*args) if quiet: - assert "run all" not in result.stdout.str() + result.stdout.no_fnmatch_line("*run all*") else: assert "rerun previous" in result.stdout.str() @@ -678,24 +643,18 @@ class TestLastFailed(object): return sorted(config.cache.get("cache/lastfailed", {})) def test_cache_cumulative(self, testdir): - """ - Test workflow where user fixes errors gradually file by file using --lf. - """ + """Test workflow where user fixes errors gradually file by file using --lf.""" # 1. initial run test_bar = testdir.makepyfile( test_bar=""" - def test_bar_1(): - pass - def test_bar_2(): - assert 0 + def test_bar_1(): pass + def test_bar_2(): assert 0 """ ) test_foo = testdir.makepyfile( test_foo=""" - def test_foo_3(): - pass - def test_foo_4(): - assert 0 + def test_foo_3(): pass + def test_foo_4(): assert 0 """ ) testdir.runpytest() @@ -707,10 +666,8 @@ class TestLastFailed(object): # 2. fix test_bar_2, run only test_bar.py testdir.makepyfile( test_bar=""" - def test_bar_1(): - pass - def test_bar_2(): - pass + def test_bar_1(): pass + def test_bar_2(): pass """ ) result = testdir.runpytest(test_bar) @@ -719,20 +676,30 @@ class TestLastFailed(object): assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] result = testdir.runpytest("--last-failed") - result.stdout.fnmatch_lines(["*1 failed, 1 deselected*"]) + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "run-last-failure: rerun previous 1 failure (skipped 1 file)", + "*= 1 failed in *", + ] + ) assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] # 3. fix test_foo_4, run only test_foo.py test_foo = testdir.makepyfile( test_foo=""" - def test_foo_3(): - pass - def test_foo_4(): - pass + def test_foo_3(): pass + def test_foo_4(): pass """ ) result = testdir.runpytest(test_foo, "--last-failed") - result.stdout.fnmatch_lines(["*1 passed, 1 deselected*"]) + result.stdout.fnmatch_lines( + [ + "collected 2 items / 1 deselected / 1 selected", + "run-last-failure: rerun previous 1 failure", + "*= 1 passed, 1 deselected in *", + ] + ) assert self.get_cached_last_failed(testdir) == [] result = testdir.runpytest("--last-failed") @@ -742,10 +709,8 @@ class TestLastFailed(object): def test_lastfailed_no_failures_behavior_all_passed(self, testdir): testdir.makepyfile( """ - def test_1(): - assert True - def test_2(): - assert True + def test_1(): pass + def test_2(): pass """ ) result = testdir.runpytest() @@ -754,23 +719,38 @@ class TestLastFailed(object): result.stdout.fnmatch_lines(["*2 passed*"]) result = testdir.runpytest("--lf", "--lfnf", "all") result.stdout.fnmatch_lines(["*2 passed*"]) + + # Ensure the list passed to pytest_deselected is a copy, + # and not a reference which is cleared right after. + testdir.makeconftest( + """ + deselected = [] + + def pytest_deselected(items): + global deselected + deselected = items + + def pytest_sessionfinish(): + print("\\ndeselected={}".format(len(deselected))) + """ + ) + result = testdir.runpytest("--lf", "--lfnf", "none") result.stdout.fnmatch_lines( [ "collected 2 items / 2 deselected", "run-last-failure: no previously failed tests, deselecting all items.", + "deselected=2", "* 2 deselected in *", ] ) - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED def test_lastfailed_no_failures_behavior_empty_cache(self, testdir): testdir.makepyfile( """ - def test_1(): - assert True - def test_2(): - assert False + def test_1(): pass + def test_2(): assert 0 """ ) result = testdir.runpytest("--lf", "--cache-clear") @@ -809,9 +789,9 @@ class TestLastFailed(object): result = testdir.runpytest("--lf") result.stdout.fnmatch_lines( [ - "collected 5 items / 3 deselected / 2 selected", + "collected 2 items", "run-last-failure: rerun previous 2 failures (skipped 1 file)", - "*2 failed*3 deselected*", + "*= 2 failed in *", ] ) @@ -826,9 +806,9 @@ class TestLastFailed(object): result = testdir.runpytest("--lf") result.stdout.fnmatch_lines( [ - "collected 5 items / 3 deselected / 2 selected", + "collected 2 items", "run-last-failure: rerun previous 2 failures (skipped 2 files)", - "*2 failed*3 deselected*", + "*= 2 failed in *", ] ) @@ -863,81 +843,202 @@ class TestLastFailed(object): ] ) - # Remove/rename test. + # Remove/rename test: collects the file again. testdir.makepyfile(**{"pkg1/test_1.py": """def test_renamed(): assert 0"""}) - result = testdir.runpytest("--lf") + result = testdir.runpytest("--lf", "-rf") + result.stdout.fnmatch_lines( + [ + "collected 2 items", + "run-last-failure: 1 known failures not in selected tests", + "pkg1/test_1.py F *", + "pkg1/test_2.py . *", + "FAILED pkg1/test_1.py::test_renamed - assert 0", + "* 1 failed, 1 passed in *", + ] + ) + + result = testdir.runpytest("--lf", "--co") result.stdout.fnmatch_lines( [ "collected 1 item", - "run-last-failure: 1 known failures not in selected tests (skipped 1 file)", - "* 1 failed in *", + "run-last-failure: rerun previous 1 failure (skipped 1 file)", + "", + "<Module pkg1/test_1.py>", + " <Function test_renamed>", ] ) + def test_lastfailed_args_with_deselected(self, testdir: Testdir) -> None: + """Test regression with --lf running into NoMatch error. + + This was caused by it not collecting (non-failed) nodes given as + arguments. + """ + testdir.makepyfile( + **{ + "pkg1/test_1.py": """ + def test_pass(): pass + def test_fail(): assert 0 + """, + } + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) + assert result.ret == 1 + + result = testdir.runpytest("pkg1/test_1.py::test_pass", "--lf", "--co") + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "*collected 1 item", + "run-last-failure: 1 known failures not in selected tests", + "", + "<Module pkg1/test_1.py>", + " <Function test_pass>", + ], + consecutive=True, + ) + + result = testdir.runpytest( + "pkg1/test_1.py::test_pass", "pkg1/test_1.py::test_fail", "--lf", "--co" + ) + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "collected 2 items / 1 deselected / 1 selected", + "run-last-failure: rerun previous 1 failure", + "", + "<Module pkg1/test_1.py>", + " <Function test_fail>", + "*= 1 deselected in *", + ], + ) -class TestNewFirst(object): + def test_lastfailed_with_class_items(self, testdir: Testdir) -> None: + """Test regression with --lf deselecting whole classes.""" + testdir.makepyfile( + **{ + "pkg1/test_1.py": """ + class TestFoo: + def test_pass(self): pass + def test_fail(self): assert 0 + + def test_other(): assert 0 + """, + } + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["collected 3 items", "* 2 failed, 1 passed in *"]) + assert result.ret == 1 + + result = testdir.runpytest("--lf", "--co") + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "collected 3 items / 1 deselected / 2 selected", + "run-last-failure: rerun previous 2 failures", + "", + "<Module pkg1/test_1.py>", + " <Class TestFoo>", + " <Function test_fail>", + " <Function test_other>", + "", + "*= 1 deselected in *", + ], + consecutive=True, + ) + + def test_lastfailed_with_all_filtered(self, testdir: Testdir) -> None: + testdir.makepyfile( + **{ + "pkg1/test_1.py": """ + def test_fail(): assert 0 + def test_pass(): pass + """, + } + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) + assert result.ret == 1 + + # Remove known failure. + testdir.makepyfile( + **{ + "pkg1/test_1.py": """ + def test_pass(): pass + """, + } + ) + result = testdir.runpytest("--lf", "--co") + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "run-last-failure: 1 known failures not in selected tests", + "", + "<Module pkg1/test_1.py>", + " <Function test_pass>", + "", + "*= no tests ran in*", + ], + consecutive=True, + ) + assert result.ret == 0 + + +class TestNewFirst: def test_newfirst_usecase(self, testdir): testdir.makepyfile( **{ "test_1/test_1.py": """ def test_1(): assert 1 - def test_2(): assert 1 - def test_3(): assert 1 """, "test_2/test_2.py": """ def test_1(): assert 1 - def test_2(): assert 1 - def test_3(): assert 1 """, } ) - testdir.tmpdir.join("test_1/test_1.py").setmtime(1) result = testdir.runpytest("-v") result.stdout.fnmatch_lines( - [ - "*test_1/test_1.py::test_1 PASSED*", - "*test_1/test_1.py::test_2 PASSED*", - "*test_1/test_1.py::test_3 PASSED*", - "*test_2/test_2.py::test_1 PASSED*", - "*test_2/test_2.py::test_2 PASSED*", - "*test_2/test_2.py::test_3 PASSED*", - ] + ["*test_1/test_1.py::test_1 PASSED*", "*test_2/test_2.py::test_1 PASSED*"] ) result = testdir.runpytest("-v", "--nf") - result.stdout.fnmatch_lines( - [ - "*test_2/test_2.py::test_1 PASSED*", - "*test_2/test_2.py::test_2 PASSED*", - "*test_2/test_2.py::test_3 PASSED*", - "*test_1/test_1.py::test_1 PASSED*", - "*test_1/test_1.py::test_2 PASSED*", - "*test_1/test_1.py::test_3 PASSED*", - ] + ["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"] ) testdir.tmpdir.join("test_1/test_1.py").write( - "def test_1(): assert 1\n" - "def test_2(): assert 1\n" - "def test_3(): assert 1\n" - "def test_4(): assert 1\n" + "def test_1(): assert 1\n" "def test_2(): assert 1\n" ) testdir.tmpdir.join("test_1/test_1.py").setmtime(1) - result = testdir.runpytest("-v", "--nf") + result = testdir.runpytest("--nf", "--collect-only", "-q") + result.stdout.fnmatch_lines( + [ + "test_1/test_1.py::test_2", + "test_2/test_2.py::test_1", + "test_1/test_1.py::test_1", + ] + ) + # Newest first with (plugin) pytest_collection_modifyitems hook. + testdir.makepyfile( + myplugin=""" + def pytest_collection_modifyitems(items): + items[:] = sorted(items, key=lambda item: item.nodeid) + print("new_items:", [x.nodeid for x in items]) + """ + ) + testdir.syspathinsert() + result = testdir.runpytest("--nf", "-p", "myplugin", "--collect-only", "-q") result.stdout.fnmatch_lines( [ - "*test_1/test_1.py::test_4 PASSED*", - "*test_2/test_2.py::test_1 PASSED*", - "*test_2/test_2.py::test_2 PASSED*", - "*test_2/test_2.py::test_3 PASSED*", - "*test_1/test_1.py::test_1 PASSED*", - "*test_1/test_1.py::test_2 PASSED*", - "*test_1/test_1.py::test_3 PASSED*", + "new_items: *test_1.py*test_1.py*test_2.py*", + "test_1/test_1.py::test_2", + "test_2/test_2.py::test_1", + "test_1/test_1.py::test_1", ] ) @@ -970,7 +1071,6 @@ class TestNewFirst(object): ) result = testdir.runpytest("-v", "--nf") - result.stdout.fnmatch_lines( [ "*test_2/test_2.py::test_1[1*", @@ -987,8 +1087,13 @@ class TestNewFirst(object): ) testdir.tmpdir.join("test_1/test_1.py").setmtime(1) - result = testdir.runpytest("-v", "--nf") + # Running only a subset does not forget about existing ones. + result = testdir.runpytest("-v", "--nf", "test_2/test_2.py") + result.stdout.fnmatch_lines( + ["*test_2/test_2.py::test_1[1*", "*test_2/test_2.py::test_1[2*"] + ) + result = testdir.runpytest("-v", "--nf") result.stdout.fnmatch_lines( [ "*test_1/test_1.py::test_1[3*", @@ -1000,29 +1105,19 @@ class TestNewFirst(object): ) -class TestReadme(object): +class TestReadme: def check_readme(self, testdir): config = testdir.parseconfigure() readme = config.cache._cachedir.joinpath("README.md") return readme.is_file() def test_readme_passed(self, testdir): - testdir.makepyfile( - """ - def test_always_passes(): - assert 1 - """ - ) + testdir.makepyfile("def test_always_passes(): pass") testdir.runpytest() assert self.check_readme(testdir) is True def test_readme_failed(self, testdir): - testdir.makepyfile( - """ - def test_always_fails(): - assert 0 - """ - ) + testdir.makepyfile("def test_always_fails(): assert 0") testdir.runpytest() assert self.check_readme(testdir) is True @@ -1034,12 +1129,12 @@ def test_gitignore(testdir): config = testdir.parseconfig() cache = Cache.for_config(config) cache.set("foo", "bar") - msg = "# Created by pytest automatically.\n*" + msg = "# Created by pytest automatically.\n*\n" gitignore_path = cache._cachedir.joinpath(".gitignore") assert gitignore_path.read_text(encoding="UTF-8") == msg # Does not overwrite existing/custom one. - gitignore_path.write_text(u"custom") + gitignore_path.write_text("custom") cache.set("something", "else") assert gitignore_path.read_text(encoding="UTF-8") == "custom" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_capture.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_capture.py index 01123bf5b40..5f820d8465b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_capture.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_capture.py @@ -1,60 +1,60 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import contextlib import io import os -import pickle import subprocess import sys import textwrap from io import UnsupportedOperation - -import py -from six import text_type +from typing import BinaryIO +from typing import cast +from typing import Generator +from typing import TextIO import pytest from _pytest import capture +from _pytest.capture import _get_multicapture from _pytest.capture import CaptureManager -from _pytest.compat import _PY3 -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.capture import CaptureResult +from _pytest.capture import MultiCapture +from _pytest.config import ExitCode +from _pytest.pytester import Testdir # note: py.io capture tests where copied from # pylib 1.4.20.dev2 (rev 13d9af95547e) -needsosdup = pytest.mark.skipif( - not hasattr(os, "dup"), reason="test needs os.dup, not available on this platform" -) - - -def StdCaptureFD(out=True, err=True, in_=True): - return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture) +def StdCaptureFD( + out: bool = True, err: bool = True, in_: bool = True +) -> MultiCapture[str]: + return capture.MultiCapture( + in_=capture.FDCapture(0) if in_ else None, + out=capture.FDCapture(1) if out else None, + err=capture.FDCapture(2) if err else None, + ) -def StdCapture(out=True, err=True, in_=True): - return capture.MultiCapture(out, err, in_, Capture=capture.SysCapture) +def StdCapture( + out: bool = True, err: bool = True, in_: bool = True +) -> MultiCapture[str]: + return capture.MultiCapture( + in_=capture.SysCapture(0) if in_ else None, + out=capture.SysCapture(1) if out else None, + err=capture.SysCapture(2) if err else None, + ) -class TestCaptureManager(object): - def test_getmethod_default_no_fd(self, monkeypatch): - from _pytest.capture import pytest_addoption - from _pytest.config.argparsing import Parser +def TeeStdCapture( + out: bool = True, err: bool = True, in_: bool = True +) -> MultiCapture[str]: + return capture.MultiCapture( + in_=capture.SysCapture(0, tee=True) if in_ else None, + out=capture.SysCapture(1, tee=True) if out else None, + err=capture.SysCapture(2, tee=True) if err else None, + ) - parser = Parser() - pytest_addoption(parser) - default = parser._groups[0].options[0].default - assert default == "fd" if hasattr(os, "dup") else "sys" - parser = Parser() - monkeypatch.delattr(os, "dup", raising=False) - pytest_addoption(parser) - assert parser._groups[0].options[0].default == "sys" - @pytest.mark.parametrize( - "method", ["no", "sys", pytest.param("fd", marks=needsosdup)] - ) +class TestCaptureManager: + @pytest.mark.parametrize("method", ["no", "sys", "fd"]) def test_capturing_basic_api(self, method): capouter = StdCaptureFD() old = sys.stdout, sys.stderr, sys.stdin @@ -84,7 +84,6 @@ class TestCaptureManager(object): finally: capouter.stop_capturing() - @needsosdup def test_init_capturing(self): capouter = StdCaptureFD() try: @@ -98,21 +97,15 @@ class TestCaptureManager(object): @pytest.mark.parametrize("method", ["fd", "sys"]) def test_capturing_unicode(testdir, method): - if hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (2, 2): - pytest.xfail("does not work on pypy < 2.2") - if sys.version_info >= (3, 0): - obj = "'b\u00f6y'" - else: - obj = "u'\u00f6y'" + obj = "'b\u00f6y'" testdir.makepyfile( - """ - # -*- coding: utf-8 -*- + """\ # taken from issue 227 from nosetests def test_unicode(): import sys print(sys.stdout) print(%s) - """ + """ % obj ) result = testdir.runpytest("--capture=%s" % method) @@ -122,10 +115,10 @@ def test_capturing_unicode(testdir, method): @pytest.mark.parametrize("method", ["fd", "sys"]) def test_capturing_bytes_in_utf8_encoding(testdir, method): testdir.makepyfile( - """ + """\ def test_unicode(): print('b\\u00f6y') - """ + """ ) result = testdir.runpytest("--capture=%s" % method) result.stdout.fnmatch_lines(["*1 passed*"]) @@ -152,7 +145,7 @@ def test_collect_capturing(testdir): ) -class TestPerTestCapturing(object): +class TestPerTestCapturing: def test_capture_and_fixtures(self, testdir): p = testdir.makepyfile( """ @@ -295,7 +288,7 @@ class TestPerTestCapturing(object): ) -class TestLoggingInteraction(object): +class TestLoggingInteraction: def test_logging_stream_ownership(self, testdir): p = testdir.makepyfile( """\ @@ -372,7 +365,7 @@ class TestLoggingInteraction(object): ) # make sure that logging is still captured in tests result = testdir.runpytest_subprocess("-s", "-p", "no:capturelog") - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stderr.fnmatch_lines(["WARNING*hello435*"]) assert "operation on closed file" not in result.stderr.str() @@ -433,7 +426,7 @@ class TestLoggingInteraction(object): ) -class TestCaptureFixture(object): +class TestCaptureFixture: @pytest.mark.parametrize("opt", [[], ["-s"]]) def test_std_functional(self, testdir, opt): reprec = testdir.inline_runsource( @@ -443,7 +436,7 @@ class TestCaptureFixture(object): out, err = capsys.readouterr() assert out.startswith("42") """, - *opt + *opt, ) reprec.assertoutcome(passed=1) @@ -463,7 +456,7 @@ class TestCaptureFixture(object): "E*capfd*capsys*same*time*", "*ERROR*setup*test_two*", "E*capsys*capfd*same*time*", - "*2 error*", + "*2 errors*", ] ) @@ -483,9 +476,9 @@ class TestCaptureFixture(object): result.stdout.fnmatch_lines( [ "*test_one*", - "*capsys*capfd*same*time*", + "E * cannot use capfd and capsys at the same time", "*test_two*", - "*capfd*capsys*same*time*", + "E * cannot use capsys and capfd at the same time", "*2 failed in*", ] ) @@ -516,13 +509,12 @@ class TestCaptureFixture(object): result = testdir.runpytest(p) result.stdout.fnmatch_lines(["xxx42xxx"]) - @needsosdup def test_stdfd_functional(self, testdir): reprec = testdir.inline_runsource( """\ def test_hello(capfd): import os - os.write(1, "42".encode('ascii')) + os.write(1, b"42") out, err = capfd.readouterr() assert out.startswith("42") capfd.close() @@ -530,7 +522,12 @@ class TestCaptureFixture(object): ) reprec.assertoutcome(passed=1) - @needsosdup + @pytest.mark.parametrize("nl", ("\n", "\r\n", "\r")) + def test_cafd_preserves_newlines(self, capfd, nl): + print("test", end=nl) + out, err = capfd.readouterr() + assert out.endswith(nl) + def test_capfdbinary(self, testdir): reprec = testdir.inline_runsource( """\ @@ -545,39 +542,39 @@ class TestCaptureFixture(object): ) reprec.assertoutcome(passed=1) - @pytest.mark.skipif( - sys.version_info < (3,), reason="only have capsysbinary in python 3" - ) def test_capsysbinary(self, testdir): - reprec = testdir.inline_runsource( - """\ + p1 = testdir.makepyfile( + r""" def test_hello(capsysbinary): import sys - # some likely un-decodable bytes - sys.stdout.buffer.write(b'\\xfe\\x98\\x20') + + sys.stdout.buffer.write(b'hello') + + # Some likely un-decodable bytes. + sys.stdout.buffer.write(b'\xfe\x98\x20') + + sys.stdout.buffer.flush() + + # Ensure writing in text mode still works and is captured. + # https://github.com/pytest-dev/pytest/issues/6871 + print("world", flush=True) + out, err = capsysbinary.readouterr() - assert out == b'\\xfe\\x98\\x20' + assert out == b'hello\xfe\x98\x20world\n' assert err == b'' - """ - ) - reprec.assertoutcome(passed=1) - @pytest.mark.skipif( - sys.version_info >= (3,), reason="only have capsysbinary in python 3" - ) - def test_capsysbinary_forbidden_in_python2(self, testdir): - testdir.makepyfile( - """\ - def test_hello(capsysbinary): - pass + print("stdout after") + print("stderr after", file=sys.stderr) """ ) - result = testdir.runpytest() + result = testdir.runpytest(str(p1), "-rA") result.stdout.fnmatch_lines( [ - "*test_hello*", - "*capsysbinary is only supported on Python 3*", - "*1 error in*", + "*- Captured stdout call -*", + "stdout after", + "*- Captured stderr call -*", + "stderr after", + "*= 1 passed in *", ] ) @@ -591,13 +588,12 @@ class TestCaptureFixture(object): result = testdir.runpytest(p) result.stdout.fnmatch_lines(["*test_partial_setup_failure*", "*1 error*"]) - @needsosdup def test_keyboardinterrupt_disables_capturing(self, testdir): p = testdir.makepyfile( """\ def test_hello(capfd): import os - os.write(1, str(42).encode('ascii')) + os.write(1, b'42') raise KeyboardInterrupt() """ ) @@ -637,26 +633,48 @@ class TestCaptureFixture(object): ) args = ("-s",) if no_capture else () result = testdir.runpytest_subprocess(*args) - result.stdout.fnmatch_lines( - """ - *while capture is disabled* - """ - ) - assert "captured before" not in result.stdout.str() - assert "captured after" not in result.stdout.str() + result.stdout.fnmatch_lines(["*while capture is disabled*", "*= 2 passed in *"]) + result.stdout.no_fnmatch_line("*captured before*") + result.stdout.no_fnmatch_line("*captured after*") if no_capture: assert "test_normal executed" in result.stdout.str() else: - assert "test_normal executed" not in result.stdout.str() + result.stdout.no_fnmatch_line("*test_normal executed*") - @pytest.mark.parametrize("fixture", ["capsys", "capfd"]) - def test_fixture_use_by_other_fixtures(self, testdir, fixture): + def test_disabled_capture_fixture_twice(self, testdir: Testdir) -> None: + """Test that an inner disabled() exit doesn't undo an outer disabled(). + + Issue #7148. """ - Ensure that capsys and capfd can be used by other fixtures during setup and teardown. + testdir.makepyfile( + """ + def test_disabled(capfd): + print('captured before') + with capfd.disabled(): + print('while capture is disabled 1') + with capfd.disabled(): + print('while capture is disabled 2') + print('while capture is disabled 1 after') + print('captured after') + assert capfd.readouterr() == ('captured before\\ncaptured after\\n', '') """ + ) + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines( + [ + "*while capture is disabled 1", + "*while capture is disabled 2", + "*while capture is disabled 1 after", + ], + consecutive=True, + ) + + @pytest.mark.parametrize("fixture", ["capsys", "capfd"]) + def test_fixture_use_by_other_fixtures(self, testdir, fixture): + """Ensure that capsys and capfd can be used by other fixtures during + setup and teardown.""" testdir.makepyfile( """\ - from __future__ import print_function import sys import pytest @@ -684,8 +702,8 @@ class TestCaptureFixture(object): ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines(["*1 passed*"]) - assert "stdout contents begin" not in result.stdout.str() - assert "stderr contents begin" not in result.stdout.str() + result.stdout.no_fnmatch_line("*stdout contents begin*") + result.stdout.no_fnmatch_line("*stderr contents begin*") @pytest.mark.parametrize("cap", ["capsys", "capfd"]) def test_fixture_use_by_other_fixtures_teardown(self, testdir, cap): @@ -731,20 +749,6 @@ def test_setup_failure_does_not_kill_capturing(testdir): result.stdout.fnmatch_lines(["*ValueError(42)*", "*1 error*"]) -def test_fdfuncarg_skips_on_no_osdup(testdir): - testdir.makepyfile( - """ - import os - if hasattr(os, 'dup'): - del os.dup - def test_hello(capfd): - pass - """ - ) - result = testdir.runpytest_subprocess("--capture=no") - result.stdout.fnmatch_lines(["*1 skipped*"]) - - def test_capture_conftest_runtest_setup(testdir): testdir.makeconftest( """ @@ -755,7 +759,7 @@ def test_capture_conftest_runtest_setup(testdir): testdir.makepyfile("def test_func(): pass") result = testdir.runpytest() assert result.ret == 0 - assert "hello19" not in result.stdout.str() + result.stdout.no_fnmatch_line("*hello19*") def test_capture_badoutput_issue412(testdir): @@ -769,7 +773,7 @@ def test_capture_badoutput_issue412(testdir): assert 0 """ ) - result = testdir.runpytest("--cap=fd") + result = testdir.runpytest("--capture=fd") result.stdout.fnmatch_lines( """ *def test_func* @@ -833,7 +837,7 @@ def test_error_during_readouterr(testdir): ) -class TestCaptureIO(object): +class TestCaptureIO: def test_text(self): f = capture.CaptureIO() f.write("hello") @@ -843,17 +847,9 @@ class TestCaptureIO(object): def test_unicode_and_str_mixture(self): f = capture.CaptureIO() - if sys.version_info >= (3, 0): - f.write("\u00f6") - pytest.raises(TypeError, f.write, b"hello") - else: - f.write(u"\u00f6") - f.write(b"hello") - s = f.getvalue() - f.close() - assert isinstance(s, text_type) + f.write("\u00f6") + pytest.raises(TypeError, f.write, b"hello") - @pytest.mark.skipif(sys.version_info[0] == 2, reason="python 3 only behaviour") def test_write_bytes_to_buffer(self): """In python3, stdout / stderr are text io wrappers (exposing a buffer property of the underlying bytestream). See issue #1407 @@ -863,103 +859,85 @@ class TestCaptureIO(object): assert f.getvalue() == "foo\r\n" -def test_dontreadfrominput(): - from _pytest.capture import DontReadFromInput +class TestTeeCaptureIO(TestCaptureIO): + def test_text(self): + sio = io.StringIO() + f = capture.TeeCaptureIO(sio) + f.write("hello") + s1 = f.getvalue() + assert s1 == "hello" + s2 = sio.getvalue() + assert s2 == s1 + f.close() + sio.close() - f = DontReadFromInput() - assert not f.isatty() - pytest.raises(IOError, f.read) - pytest.raises(IOError, f.readlines) - iter_f = iter(f) - pytest.raises(IOError, next, iter_f) - pytest.raises(UnsupportedOperation, f.fileno) - f.close() # just for completeness + def test_unicode_and_str_mixture(self): + sio = io.StringIO() + f = capture.TeeCaptureIO(sio) + f.write("\u00f6") + pytest.raises(TypeError, f.write, b"hello") -@pytest.mark.skipif("sys.version_info < (3,)", reason="python2 has no buffer") -def test_dontreadfrominput_buffer_python3(): +def test_dontreadfrominput(): from _pytest.capture import DontReadFromInput f = DontReadFromInput() - fb = f.buffer - assert not fb.isatty() - pytest.raises(IOError, fb.read) - pytest.raises(IOError, fb.readlines) + assert f.buffer is f + assert not f.isatty() + pytest.raises(OSError, f.read) + pytest.raises(OSError, f.readlines) iter_f = iter(f) - pytest.raises(IOError, next, iter_f) - pytest.raises(ValueError, fb.fileno) - f.close() # just for completeness - - -@pytest.mark.skipif("sys.version_info >= (3,)", reason="python2 has no buffer") -def test_dontreadfrominput_buffer_python2(): - from _pytest.capture import DontReadFromInput - - f = DontReadFromInput() - with pytest.raises(AttributeError): - f.buffer + pytest.raises(OSError, next, iter_f) + pytest.raises(UnsupportedOperation, f.fileno) f.close() # just for completeness -@pytest.yield_fixture -def tmpfile(testdir): +def test_captureresult() -> None: + cr = CaptureResult("out", "err") + assert len(cr) == 2 + assert cr.out == "out" + assert cr.err == "err" + out, err = cr + assert out == "out" + assert err == "err" + assert cr[0] == "out" + assert cr[1] == "err" + assert cr == cr + assert cr == CaptureResult("out", "err") + assert cr != CaptureResult("wrong", "err") + assert cr == ("out", "err") + assert cr != ("out", "wrong") + assert hash(cr) == hash(CaptureResult("out", "err")) + assert hash(cr) == hash(("out", "err")) + assert hash(cr) != hash(("out", "wrong")) + assert cr < ("z",) + assert cr < ("z", "b") + assert cr < ("z", "b", "c") + assert cr.count("err") == 1 + assert cr.count("wrong") == 0 + assert cr.index("err") == 1 + with pytest.raises(ValueError): + assert cr.index("wrong") == 0 + assert next(iter(cr)) == "out" + assert cr._replace(err="replaced") == ("out", "replaced") + + +@pytest.fixture +def tmpfile(testdir) -> Generator[BinaryIO, None, None]: f = testdir.makepyfile("").open("wb+") yield f if not f.closed: f.close() -@needsosdup -def test_dupfile(tmpfile): - flist = [] - for i in range(5): - nf = capture.safe_text_dupfile(tmpfile, "wb") - assert nf != tmpfile - assert nf.fileno() != tmpfile.fileno() - assert nf not in flist - print(i, end="", file=nf) - flist.append(nf) - - fname_open = flist[0].name - assert fname_open == repr(flist[0].buffer) - - for i in range(5): - f = flist[i] - f.close() - fname_closed = flist[0].name - assert fname_closed == repr(flist[0].buffer) - assert fname_closed != fname_open - tmpfile.seek(0) - s = tmpfile.read() - assert "01234" in repr(s) - tmpfile.close() - assert fname_closed == repr(flist[0].buffer) - - -def test_dupfile_on_bytesio(): - bio = io.BytesIO() - f = capture.safe_text_dupfile(bio, "wb") - f.write("hello") - assert bio.getvalue() == b"hello" - assert "BytesIO object" in f.name - - -def test_dupfile_on_textio(): - tio = py.io.TextIO() - f = capture.safe_text_dupfile(tio, "wb") - f.write("hello") - assert tio.getvalue() == "hello" - assert not hasattr(f, "name") - - @contextlib.contextmanager def lsof_check(): pid = os.getpid() try: out = subprocess.check_output(("lsof", "-p", str(pid))).decode() - except (OSError, subprocess.CalledProcessError, UnicodeDecodeError): + except (OSError, subprocess.CalledProcessError, UnicodeDecodeError) as exc: # about UnicodeDecodeError, see note on pytester - pytest.skip("could not run 'lsof'") + pytest.skip("could not run 'lsof' ({!r})".format(exc)) yield out2 = subprocess.check_output(("lsof", "-p", str(pid))).decode() len1 = len([x for x in out.split("\n") if "REG" in x]) @@ -967,17 +945,14 @@ def lsof_check(): assert len2 < len1 + 3, out2 -class TestFDCapture(object): - pytestmark = needsosdup - +class TestFDCapture: def test_simple(self, tmpfile): fd = tmpfile.fileno() cap = capture.FDCapture(fd) data = b"hello" os.write(fd, data) - s = cap.snap() + pytest.raises(AssertionError, cap.snap) cap.done() - assert not s cap = capture.FDCapture(fd) cap.start() os.write(fd, data) @@ -998,7 +973,7 @@ class TestFDCapture(object): fd = tmpfile.fileno() cap = capture.FDCapture(fd) cap.done() - pytest.raises(ValueError, cap.start) + pytest.raises(AssertionError, cap.start) def test_stderr(self): cap = capture.FDCapture(2) @@ -1008,7 +983,7 @@ class TestFDCapture(object): cap.done() assert s == "hello\n" - def test_stdin(self, tmpfile): + def test_stdin(self): cap = capture.FDCapture(0) cap.start() x = os.read(0, 100).strip() @@ -1021,7 +996,7 @@ class TestFDCapture(object): cap.start() tmpfile.write(data1) tmpfile.flush() - cap.writeorg(data2) + cap.writeorg(data2.decode("ascii")) scap = cap.snap() cap.done() assert scap == data1.decode("ascii") @@ -1029,7 +1004,7 @@ class TestFDCapture(object): stmp = stmp_file.read() assert stmp == data2 - def test_simple_resume_suspend(self, tmpfile): + def test_simple_resume_suspend(self): with saved_fd(1): cap = capture.FDCapture(1) cap.start() @@ -1049,7 +1024,19 @@ class TestFDCapture(object): assert s == "but now yes\n" cap.suspend() cap.done() - pytest.raises(AttributeError, cap.suspend) + pytest.raises(AssertionError, cap.suspend) + + assert repr(cap) == ( + "<FDCapture 1 oldfd={} _state='done' tmpfile={!r}>".format( + cap.targetfd_save, cap.tmpfile + ) + ) + # Should not crash with missing "_old". + assert repr(cap.syscapture) == ( + "<SysCapture stdout _old=<UNSET> _state='done' tmpfile={!r}>".format( + cap.syscapture.tmpfile + ) + ) def test_capfd_sys_stdout_mode(self, capfd): assert "b" not in sys.stdout.mode @@ -1065,7 +1052,7 @@ def saved_fd(fd): os.close(new_fd) -class TestStdCapture(object): +class TestStdCapture: captureclass = staticmethod(StdCapture) @contextlib.contextmanager @@ -1116,17 +1103,7 @@ class TestStdCapture(object): with self.getcapture() as cap: print("hxąć") out, err = cap.readouterr() - assert out == u"hxąć\n" - - @pytest.mark.skipif( - "sys.version_info >= (3,)", reason="text output different for bytes on python3" - ) - def test_capturing_readouterr_decode_error_handling(self): - with self.getcapture() as cap: - # triggered an internal error in pytest - print("\xa6") - out, err = cap.readouterr() - assert out == u"\ufffd\n" + assert out == "hxąć\n" def test_reset_twice_error(self): with self.getcapture() as cap: @@ -1190,21 +1167,37 @@ class TestStdCapture(object): print("XXX which indicates an error in the underlying capturing") print("XXX mechanisms") with self.getcapture(): - pytest.raises(IOError, sys.stdin.read) + pytest.raises(OSError, sys.stdin.read) + + +class TestTeeStdCapture(TestStdCapture): + captureclass = staticmethod(TeeStdCapture) + + def test_capturing_error_recursive(self): + r"""For TeeStdCapture since we passthrough stderr/stdout, cap1 + should get all output, while cap2 should only get "cap2\n".""" + + with self.getcapture() as cap1: + print("cap1") + with self.getcapture() as cap2: + print("cap2") + out2, err2 = cap2.readouterr() + out1, err1 = cap1.readouterr() + assert out1 == "cap1\ncap2\n" + assert out2 == "cap2\n" class TestStdCaptureFD(TestStdCapture): - pytestmark = needsosdup captureclass = staticmethod(StdCaptureFD) def test_simple_only_fd(self, testdir): testdir.makepyfile( - """ + """\ import os def test_x(): - os.write(1, "hello\\n".encode("ascii")) + os.write(1, b"hello\\n") assert 0 - """ + """ ) result = testdir.runpytest_subprocess() result.stdout.fnmatch_lines( @@ -1233,37 +1226,47 @@ class TestStdCaptureFD(TestStdCapture): with lsof_check(): for i in range(10): cap = StdCaptureFD() + cap.start_capturing() cap.stop_capturing() -class TestStdCaptureFDinvalidFD(object): - pytestmark = needsosdup - +class TestStdCaptureFDinvalidFD: def test_stdcapture_fd_invalid_fd(self, testdir): testdir.makepyfile( """ import os + from fnmatch import fnmatch from _pytest import capture def StdCaptureFD(out=True, err=True, in_=True): - return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture) + return capture.MultiCapture( + in_=capture.FDCapture(0) if in_ else None, + out=capture.FDCapture(1) if out else None, + err=capture.FDCapture(2) if err else None, + ) def test_stdout(): os.close(1) cap = StdCaptureFD(out=True, err=False, in_=False) - assert repr(cap.out) == "<FDCapture 1 oldfd=None _state=None>" + assert fnmatch(repr(cap.out), "<FDCapture 1 oldfd=* _state='initialized' tmpfile=*>") + cap.start_capturing() + os.write(1, b"stdout") + assert cap.readouterr() == ("stdout", "") cap.stop_capturing() def test_stderr(): os.close(2) cap = StdCaptureFD(out=False, err=True, in_=False) - assert repr(cap.err) == "<FDCapture 2 oldfd=None _state=None>" + assert fnmatch(repr(cap.err), "<FDCapture 2 oldfd=* _state='initialized' tmpfile=*>") + cap.start_capturing() + os.write(2, b"stderr") + assert cap.readouterr() == ("", "stderr") cap.stop_capturing() def test_stdin(): os.close(0) cap = StdCaptureFD(out=False, err=False, in_=True) - assert repr(cap.in_) == "<FDCapture 0 oldfd=None _state=None>" + assert fnmatch(repr(cap.in_), "<FDCapture 0 oldfd=* _state='initialized' tmpfile=*>") cap.stop_capturing() """ ) @@ -1271,6 +1274,37 @@ class TestStdCaptureFDinvalidFD(object): assert result.ret == 0 assert result.parseoutcomes()["passed"] == 3 + def test_fdcapture_invalid_fd_with_fd_reuse(self, testdir): + with saved_fd(1): + os.close(1) + cap = capture.FDCaptureBinary(1) + cap.start() + os.write(1, b"started") + cap.suspend() + os.write(1, b" suspended") + cap.resume() + os.write(1, b" resumed") + assert cap.snap() == b"started resumed" + cap.done() + with pytest.raises(OSError): + os.write(1, b"done") + + def test_fdcapture_invalid_fd_without_fd_reuse(self, testdir): + with saved_fd(1), saved_fd(2): + os.close(1) + os.close(2) + cap = capture.FDCaptureBinary(2) + cap.start() + os.write(2, b"started") + cap.suspend() + os.write(2, b" suspended") + cap.resume() + os.write(2, b" resumed") + assert cap.snap() == b"started resumed" + cap.done() + with pytest.raises(OSError): + os.write(2, b"done") + def test_capture_not_started_but_reset(): capsys = StdCapture() @@ -1294,12 +1328,8 @@ def test_capsys_results_accessible_by_attribute(capsys): assert capture_result.err == "eggs" -@needsosdup -@pytest.mark.parametrize("use", [True, False]) -def test_fdcapture_tmpfile_remains_the_same(tmpfile, use): - if not use: - tmpfile = True - cap = StdCaptureFD(out=False, err=tmpfile) +def test_fdcapture_tmpfile_remains_the_same() -> None: + cap = StdCaptureFD(out=False, err=True) try: cap.start_capturing() capfile = cap.err.tmpfile @@ -1310,7 +1340,6 @@ def test_fdcapture_tmpfile_remains_the_same(tmpfile, use): assert capfile2 == capfile -@needsosdup def test_close_and_capture_again(testdir): testdir.makepyfile( """ @@ -1333,18 +1362,21 @@ def test_close_and_capture_again(testdir): ) -@pytest.mark.parametrize("method", ["SysCapture", "FDCapture"]) -def test_capturing_and_logging_fundamentals(testdir, method): - if method == "StdCaptureFD" and not hasattr(os, "dup"): - pytest.skip("need os.dup") +@pytest.mark.parametrize( + "method", ["SysCapture(2)", "SysCapture(2, tee=True)", "FDCapture(2)"] +) +def test_capturing_and_logging_fundamentals(testdir, method: str) -> None: # here we check a fundamental feature p = testdir.makepyfile( """ import sys, os import py, logging from _pytest import capture - cap = capture.MultiCapture(out=False, in_=False, - Capture=capture.%s) + cap = capture.MultiCapture( + in_=None, + out=None, + err=capture.%s, + ) cap.start_capturing() logging.warning("hello1") @@ -1380,8 +1412,8 @@ def test_error_attribute_issue555(testdir): """ import sys def test_capattr(): - assert sys.stdout.errors == "strict" - assert sys.stderr.errors == "strict" + assert sys.stdout.errors == "replace" + assert sys.stderr.errors == "replace" """ ) reprec = testdir.inline_run() @@ -1392,18 +1424,18 @@ def test_error_attribute_issue555(testdir): not sys.platform.startswith("win") and sys.version_info[:2] >= (3, 6), reason="only py3.6+ on windows", ) -def test_py36_windowsconsoleio_workaround_non_standard_streams(): +def test_py36_windowsconsoleio_workaround_non_standard_streams() -> None: """ Ensure _py36_windowsconsoleio_workaround function works with objects that do not implement the full ``io``-based stream protocol, for example execnet channels (#2666). """ from _pytest.capture import _py36_windowsconsoleio_workaround - class DummyStream(object): + class DummyStream: def write(self, s): pass - stream = DummyStream() + stream = cast(TextIO, DummyStream()) _py36_windowsconsoleio_workaround(stream) @@ -1424,7 +1456,6 @@ def test_dontreadfrominput_has_encoding(testdir): def test_crash_on_closing_tmpfile_py27(testdir): p = testdir.makepyfile( """ - from __future__ import print_function import threading import sys @@ -1449,19 +1480,12 @@ def test_crash_on_closing_tmpfile_py27(testdir): printing.wait() """ ) + # Do not consider plugins like hypothesis, which might output to stderr. + testdir.monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") result = testdir.runpytest_subprocess(str(p)) assert result.ret == 0 assert result.stderr.str() == "" - assert "IOError" not in result.stdout.str() - - -def test_pickling_and_unpickling_encoded_file(): - # See https://bitbucket.org/pytest-dev/pytest/pull-request/194 - # pickle.loads() raises infinite recursion if - # EncodedFile.__getattr__ is not implemented properly - ef = capture.EncodedFile(None, None) - ef_as_str = pickle.dumps(ef) - pickle.loads(ef_as_str) + result.stdout.no_fnmatch_line("*OSError*") def test_global_capture_with_live_logging(testdir): @@ -1506,14 +1530,14 @@ def test_global_capture_with_live_logging(testdir): result = testdir.runpytest_subprocess("--log-cli-level=INFO") assert result.ret == 0 - with open("caplog", "r") as f: + with open("caplog") as f: caplog = f.read() assert "fix setup" in caplog assert "something in test" in caplog assert "fix teardown" in caplog - with open("capstdout", "r") as f: + with open("capstdout") as f: capstdout = f.read() assert "fix setup" in capstdout @@ -1566,14 +1590,64 @@ def test_typeerror_encodedfile_write(testdir): """ ) result_without_capture = testdir.runpytest("-s", str(p)) - result_with_capture = testdir.runpytest(str(p)) assert result_with_capture.ret == result_without_capture.ret + out = result_with_capture.stdout.str() + assert ("TypeError: write() argument must be str, not bytes" in out) or ( + "TypeError: unicode argument expected, got 'bytes'" in out + ) - if _PY3: - result_with_capture.stdout.fnmatch_lines( - ["E TypeError: write() argument must be str, not bytes"] - ) - else: - assert result_with_capture.ret == 0 + +def test_stderr_write_returns_len(capsys): + """Write on Encoded files, namely captured stderr, should return number of characters written.""" + assert sys.stderr.write("Foo") == 3 + + +def test_encodedfile_writelines(tmpfile: BinaryIO) -> None: + ef = capture.EncodedFile(tmpfile, encoding="utf-8") + with pytest.raises(TypeError): + ef.writelines([b"line1", b"line2"]) + assert ef.writelines(["line3", "line4"]) is None # type: ignore[func-returns-value] + ef.flush() + tmpfile.seek(0) + assert tmpfile.read() == b"line3line4" + tmpfile.close() + with pytest.raises(ValueError): + ef.read() + + +def test__get_multicapture() -> None: + assert isinstance(_get_multicapture("no"), MultiCapture) + pytest.raises(ValueError, _get_multicapture, "unknown").match( + r"^unknown capturing method: 'unknown'" + ) + + +def test_logging_while_collecting(testdir): + """Issue #6240: Calls to logging.xxx() during collection causes all logging calls to be duplicated to stderr""" + p = testdir.makepyfile( + """\ + import logging + + logging.warning("during collection") + + def test_logging(): + logging.warning("during call") + assert False + """ + ) + result = testdir.runpytest_subprocess(p) + assert result.ret == ExitCode.TESTS_FAILED + result.stdout.fnmatch_lines( + [ + "*test_*.py F*", + "====* FAILURES *====", + "____*____", + "*--- Captured log call*", + "WARNING * during call", + "*1 failed*", + ] + ) + result.stdout.no_fnmatch_line("*Captured stderr call*") + result.stdout.no_fnmatch_line("*during collection*") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_collection.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_collection.py index 0fbbbec54df..3e1b816b79e 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_collection.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_collection.py @@ -1,30 +1,25 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import pprint import sys import textwrap -import py - import pytest +from _pytest.config import ExitCode from _pytest.main import _in_venv -from _pytest.main import EXIT_INTERRUPTED -from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import Session +from _pytest.pathlib import Path +from _pytest.pathlib import symlink_or_skip +from _pytest.pytester import Testdir -class TestCollector(object): +class TestCollector: def test_collect_versus_item(self): from pytest import Collector, Item assert not issubclass(Collector, Item) assert not issubclass(Item, Collector) - def test_check_equality(self, testdir): + def test_check_equality(self, testdir: Testdir) -> None: modcol = testdir.getmodulecol( """ def test_pass(): pass @@ -38,8 +33,6 @@ class TestCollector(object): assert fn1 == fn2 assert fn1 != modcol - if sys.version_info < (3, 0): - assert cmp(fn1, fn2) == 0 # NOQA assert hash(fn1) == hash(fn2) fn3 = testdir.collect_by_name(modcol, "test_fail") @@ -48,17 +41,20 @@ class TestCollector(object): assert fn1 != fn3 for fn in fn1, fn2, fn3: - assert fn != 3 + assert isinstance(fn, pytest.Function) + assert fn != 3 # type: ignore[comparison-overlap] assert fn != modcol - assert fn != [1, 2, 3] - assert [1, 2, 3] != fn + assert fn != [1, 2, 3] # type: ignore[comparison-overlap] + assert [1, 2, 3] != fn # type: ignore[comparison-overlap] assert modcol != fn + assert testdir.collect_by_name(modcol, "doesnotexist") is None + def test_getparent(self, testdir): modcol = testdir.getmodulecol( """ - class TestClass(object): - def test_foo(): + class TestClass: + def test_foo(self): pass """ ) @@ -83,7 +79,7 @@ class TestCollector(object): pass def pytest_collect_file(path, parent): if path.ext == ".xxx": - return CustomFile(path, parent=parent) + return CustomFile.from_parent(fspath=path, parent=parent) """ ) node = testdir.getpathnode(hello) @@ -109,7 +105,7 @@ class TestCollector(object): result.stdout.fnmatch_lines(["collected 0 items", "*no tests ran in*"]) -class TestCollectFS(object): +class TestCollectFS: def test_ignored_certain_directories(self, testdir): tmpdir = testdir.tmpdir tmpdir.ensure("build", "test_notfound.py") @@ -120,8 +116,8 @@ class TestCollectFS(object): tmpdir.ensure(".whatever", "test_notfound.py") tmpdir.ensure(".bzr", "test_notfound.py") tmpdir.ensure("normal", "test_found.py") - for x in tmpdir.visit("test_*.py"): - x.write("def test_hello(): pass") + for x in Path(str(tmpdir)).rglob("test_*.py"): + x.write_text("def test_hello(): pass", "utf-8") result = testdir.runpytest("--collect-only") s = result.stdout.str() @@ -147,7 +143,7 @@ class TestCollectFS(object): # by default, ignore tests inside a virtualenv result = testdir.runpytest() - assert "test_invenv" not in result.stdout.str() + result.stdout.no_fnmatch_line("*test_invenv*") # allow test collection if user insists result = testdir.runpytest("--collect-in-virtualenv") assert "test_invenv" in result.stdout.str() @@ -173,7 +169,7 @@ class TestCollectFS(object): testfile = testdir.tmpdir.ensure(".virtual", "test_invenv.py") testfile.write("def test_hello(): pass") result = testdir.runpytest("--collect-in-virtualenv") - assert "test_invenv" not in result.stdout.str() + result.stdout.no_fnmatch_line("*test_invenv*") # ...unless the virtualenv is explicitly given on the CLI result = testdir.runpytest("--collect-in-virtualenv", ".virtual") assert "test_invenv" in result.stdout.str() @@ -246,12 +242,12 @@ class TestCollectFS(object): assert [x.name for x in items] == ["test_%s" % dirname] -class TestCollectPluginHookRelay(object): +class TestCollectPluginHookRelay: def test_pytest_collect_file(self, testdir): wascalled = [] - class Plugin(object): - def pytest_collect_file(self, path, parent): + class Plugin: + def pytest_collect_file(self, path): if not path.basename.startswith("."): # Ignore hidden files, e.g. .testmondata. wascalled.append(path) @@ -261,21 +257,8 @@ class TestCollectPluginHookRelay(object): assert len(wascalled) == 1 assert wascalled[0].ext == ".abc" - def test_pytest_collect_directory(self, testdir): - wascalled = [] - - class Plugin(object): - def pytest_collect_directory(self, path, parent): - wascalled.append(path.basename) - testdir.mkdir("hello") - testdir.mkdir("world") - pytest.main(testdir.tmpdir, plugins=[Plugin()]) - assert "hello" in wascalled - assert "world" in wascalled - - -class TestPrunetraceback(object): +class TestPrunetraceback: def test_custom_repr_failure(self, testdir): p = testdir.makepyfile( """ @@ -286,7 +269,7 @@ class TestPrunetraceback(object): """ import pytest def pytest_collect_file(path, parent): - return MyFile(path, parent) + return MyFile.from_parent(fspath=path, parent=parent) class MyError(Exception): pass class MyFile(pytest.File): @@ -324,7 +307,7 @@ class TestPrunetraceback(object): result.stdout.fnmatch_lines(["*ERROR collecting*", "*header1*"]) -class TestCustomConftests(object): +class TestCustomConftests: def test_ignore_collect_path(self, testdir): testdir.makeconftest( """ @@ -354,7 +337,7 @@ class TestCustomConftests(object): assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stdout.fnmatch_lines(["*collected 0 items*"]) def test_collectignore_exclude_on_option(self, testdir): @@ -371,8 +354,8 @@ class TestCustomConftests(object): testdir.mkdir("hello") testdir.makepyfile(test_world="def test_hello(): pass") result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED - assert "passed" not in result.stdout.str() + assert result.ret == ExitCode.NO_TESTS_COLLECTED + result.stdout.no_fnmatch_line("*passed*") result = testdir.runpytest("--XX") assert result.ret == 0 assert "passed" in result.stdout.str() @@ -391,7 +374,7 @@ class TestCustomConftests(object): testdir.makepyfile(test_world="def test_hello(): pass") testdir.makepyfile(test_welt="def test_hallo(): pass") result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stdout.fnmatch_lines(["*collected 0 items*"]) result = testdir.runpytest("--XX") assert result.ret == 0 @@ -405,12 +388,12 @@ class TestCustomConftests(object): pass def pytest_collect_file(path, parent): if path.ext == ".py": - return MyModule(path, parent) + return MyModule.from_parent(fspath=path, parent=parent) """ ) testdir.mkdir("sub") testdir.makepyfile("def test_x(): pass") - result = testdir.runpytest("--collect-only") + result = testdir.runpytest("--co") result.stdout.fnmatch_lines(["*MyModule*", "*test_x*"]) def test_pytest_collect_file_from_sister_dir(self, testdir): @@ -423,7 +406,7 @@ class TestCustomConftests(object): pass def pytest_collect_file(path, parent): if path.ext == ".py": - return MyModule1(path, parent) + return MyModule1.from_parent(fspath=path, parent=parent) """ ) conf1.move(sub1.join(conf1.basename)) @@ -434,44 +417,25 @@ class TestCustomConftests(object): pass def pytest_collect_file(path, parent): if path.ext == ".py": - return MyModule2(path, parent) + return MyModule2.from_parent(fspath=path, parent=parent) """ ) conf2.move(sub2.join(conf2.basename)) p = testdir.makepyfile("def test_x(): pass") p.copy(sub1.join(p.basename)) p.copy(sub2.join(p.basename)) - result = testdir.runpytest("--collect-only") + result = testdir.runpytest("--co") result.stdout.fnmatch_lines(["*MyModule1*", "*MyModule2*", "*test_x*"]) -class TestSession(object): - def test_parsearg(self, testdir): - p = testdir.makepyfile("def test_func(): pass") - subdir = testdir.mkdir("sub") - subdir.ensure("__init__.py") - target = subdir.join(p.basename) - p.move(target) - subdir.chdir() - config = testdir.parseconfig(p.basename) - rcol = Session(config=config) - assert rcol.fspath == subdir - parts = rcol._parsearg(p.basename) - - assert parts[0] == target - assert len(parts) == 1 - parts = rcol._parsearg(p.basename + "::test_func") - assert parts[0] == target - assert parts[1] == "test_func" - assert len(parts) == 2 - +class TestSession: def test_collect_topdir(self, testdir): p = testdir.makepyfile("def test_func(): pass") id = "::".join([p.basename, "test_func"]) # XXX migrate to collectonly? (see below) config = testdir.parseconfig(id) topdir = testdir.tmpdir - rcol = Session(config) + rcol = Session.from_config(config) assert topdir == rcol.fspath # rootid = rcol.nodeid # root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0] @@ -541,10 +505,10 @@ class TestSession(object): return # ok class SpecialFile(pytest.File): def collect(self): - return [SpecialItem(name="check", parent=self)] + return [SpecialItem.from_parent(name="check", parent=self)] def pytest_collect_file(path, parent): if path.basename == %r: - return SpecialFile(fspath=path, parent=parent) + return SpecialFile.from_parent(fspath=path, parent=parent) """ % p.basename ) @@ -636,14 +600,15 @@ class TestSession(object): assert [x.name for x in self.get_reported_items(hookrec)] == ["test_method"] -class Test_getinitialnodes(object): - def test_global_file(self, testdir, tmpdir): +class Test_getinitialnodes: + def test_global_file(self, testdir, tmpdir) -> None: x = tmpdir.ensure("x.py") with tmpdir.as_cwd(): config = testdir.parseconfigure(x) col = testdir.getnode(config, x) assert isinstance(col, pytest.Module) assert col.name == "x.py" + assert col.parent is not None assert col.parent.parent is None for col in col.listchain(): assert col.config is config @@ -670,7 +635,7 @@ class Test_getinitialnodes(object): assert col.config is config -class Test_genitems(object): +class Test_genitems: def test_check_collect_hashes(self, testdir): p = testdir.makepyfile( """ @@ -693,6 +658,8 @@ class Test_genitems(object): def test_example_items1(self, testdir): p = testdir.makepyfile( """ + import pytest + def testone(): pass @@ -701,29 +668,32 @@ class Test_genitems(object): pass class TestY(TestX): - pass + @pytest.mark.parametrize("arg0", [".["]) + def testmethod_two(self, arg0): + pass """ ) items, reprec = testdir.inline_genitems(p) - assert len(items) == 3 + assert len(items) == 4 assert items[0].name == "testone" assert items[1].name == "testmethod_one" assert items[2].name == "testmethod_one" + assert items[3].name == "testmethod_two[.[]" # let's also test getmodpath here assert items[0].getmodpath() == "testone" assert items[1].getmodpath() == "TestX.testmethod_one" assert items[2].getmodpath() == "TestY.testmethod_one" + # PR #6202: Fix incorrect result of getmodpath method. (Resolves issue #6189) + assert items[3].getmodpath() == "TestY.testmethod_two[.[]" s = items[0].getmodpath(stopatmodule=False) assert s.endswith("test_example_items1.testone") print(s) def test_class_and_functions_discovery_using_glob(self, testdir): - """ - tests that python_classes and python_functions config options work - as prefixes and glob-like patterns (issue #600). - """ + """Test that Python_classes and Python_functions config options work + as prefixes and glob-like patterns (#600).""" testdir.makeini( """ [pytest] @@ -757,18 +727,23 @@ def test_matchnodes_two_collections_same_file(testdir): class Plugin2(object): def pytest_collect_file(self, path, parent): if path.ext == ".abc": - return MyFile2(path, parent) + return MyFile2.from_parent(fspath=path, parent=parent) def pytest_collect_file(path, parent): if path.ext == ".abc": - return MyFile1(path, parent) + return MyFile1.from_parent(fspath=path, parent=parent) + + class MyFile1(pytest.File): + def collect(self): + yield Item1.from_parent(name="item1", parent=self) - class MyFile1(pytest.Item, pytest.File): - def runtest(self): - pass class MyFile2(pytest.File): def collect(self): - return [Item2("hello", parent=self)] + yield Item2.from_parent(name="item2", parent=self) + + class Item1(pytest.Item): + def runtest(self): + pass class Item2(pytest.Item): def runtest(self): @@ -779,11 +754,11 @@ def test_matchnodes_two_collections_same_file(testdir): result = testdir.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*2 passed*"]) - res = testdir.runpytest("%s::hello" % p.basename) + res = testdir.runpytest("%s::item2" % p.basename) res.stdout.fnmatch_lines(["*1 passed*"]) -class TestNodekeywords(object): +class TestNodekeywords: def test_no_under(self, testdir): modcol = testdir.getmodulecol( """ @@ -810,6 +785,43 @@ class TestNodekeywords(object): reprec = testdir.inline_run("-k repr") reprec.assertoutcome(passed=1, failed=0) + def test_keyword_matching_is_case_insensitive_by_default(self, testdir): + """Check that selection via -k EXPRESSION is case-insensitive. + + Since markers are also added to the node keywords, they too can + be matched without having to think about case sensitivity. + + """ + testdir.makepyfile( + """ + import pytest + + def test_sPeCiFiCToPiC_1(): + assert True + + class TestSpecificTopic_2: + def test(self): + assert True + + @pytest.mark.sPeCiFiCToPic_3 + def test(): + assert True + + @pytest.mark.sPeCiFiCToPic_4 + class Test: + def test(self): + assert True + + def test_failing_5(): + assert False, "This should not match" + + """ + ) + num_matching_tests = 4 + for expression in ("specifictopic", "SPECIFICTOPIC", "SpecificTopic"): + reprec = testdir.inline_run("-k " + expression) + reprec.assertoutcome(passed=num_matching_tests, failed=0) + COLLECTION_ERROR_PY_FILES = dict( test_01_failure=""" @@ -860,12 +872,16 @@ def test_exit_on_collection_with_maxfail_smaller_than_n_errors(testdir): res = testdir.runpytest("--maxfail=1") assert res.ret == 1 - res.stdout.fnmatch_lines( - ["*ERROR collecting test_02_import_error.py*", "*No module named *asdfa*"] + [ + "collected 1 item / 1 error", + "*ERROR collecting test_02_import_error.py*", + "*No module named *asdfa*", + "*! stopping after 1 failures !*", + "*= 1 error in *", + ] ) - - assert "test_03" not in res.stdout.str() + res.stdout.no_fnmatch_line("*test_03*") def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir): @@ -877,7 +893,6 @@ def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir): res = testdir.runpytest("--maxfail=4") assert res.ret == 2 - res.stdout.fnmatch_lines( [ "collected 2 items / 2 errors", @@ -885,6 +900,8 @@ def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir): "*No module named *asdfa*", "*ERROR collecting test_03_import_error.py*", "*No module named *asdfa*", + "*! Interrupted: 2 errors during collection !*", + "*= 2 errors in *", ] ) @@ -900,7 +917,7 @@ def test_continue_on_collection_errors(testdir): assert res.ret == 1 res.stdout.fnmatch_lines( - ["collected 2 items / 2 errors", "*1 failed, 1 passed, 2 error*"] + ["collected 2 items / 2 errors", "*1 failed, 1 passed, 2 errors*"] ) @@ -917,7 +934,7 @@ def test_continue_on_collection_errors_maxfail(testdir): res = testdir.runpytest("--continue-on-collection-errors", "--maxfail=3") assert res.ret == 1 - res.stdout.fnmatch_lines(["collected 2 items / 2 errors", "*1 failed, 2 error*"]) + res.stdout.fnmatch_lines(["collected 2 items / 2 errors", "*1 failed, 2 errors*"]) def test_fixture_scope_sibling_conftests(testdir): @@ -958,7 +975,7 @@ def test_collect_init_tests(testdir): result.stdout.fnmatch_lines( [ "collected 2 items", - "<Package *", + "<Package tests>", " <Module __init__.py>", " <Function test_init>", " <Module test_foo.py>", @@ -969,7 +986,7 @@ def test_collect_init_tests(testdir): result.stdout.fnmatch_lines( [ "collected 2 items", - "<Package *", + "<Package tests>", " <Module __init__.py>", " <Function test_init>", " <Module test_foo.py>", @@ -981,7 +998,7 @@ def test_collect_init_tests(testdir): result.stdout.fnmatch_lines( [ "collected 2 items", - "<Package */tests>", + "<Package tests>", " <Module __init__.py>", " <Function test_init>", " <Module test_foo.py>", @@ -993,7 +1010,7 @@ def test_collect_init_tests(testdir): result.stdout.fnmatch_lines( [ "collected 2 items", - "<Package */tests>", + "<Package tests>", " <Module __init__.py>", " <Function test_init>", " <Module test_foo.py>", @@ -1002,14 +1019,14 @@ def test_collect_init_tests(testdir): ) result = testdir.runpytest("./tests/test_foo.py", "--collect-only") result.stdout.fnmatch_lines( - ["<Package */tests>", " <Module test_foo.py>", " <Function test_foo>"] + ["<Package tests>", " <Module test_foo.py>", " <Function test_foo>"] ) - assert "test_init" not in result.stdout.str() + result.stdout.no_fnmatch_line("*test_init*") result = testdir.runpytest("./tests/__init__.py", "--collect-only") result.stdout.fnmatch_lines( - ["<Package */tests>", " <Module __init__.py>", " <Function test_init>"] + ["<Package tests>", " <Module __init__.py>", " <Function test_init>"] ) - assert "test_foo" not in result.stdout.str() + result.stdout.no_fnmatch_line("*test_foo*") def test_collect_invalid_signature_message(testdir): @@ -1117,29 +1134,21 @@ def test_collect_pyargs_with_testpaths(testdir, monkeypatch): result.stdout.fnmatch_lines(["*1 passed in*"]) -@pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="symlink not available on this platform", -) def test_collect_symlink_file_arg(testdir): - """Test that collecting a direct symlink, where the target does not match python_files works (#4325).""" + """Collect a direct symlink works even if it does not match python_files (#4325).""" real = testdir.makepyfile( real=""" def test_nodeid(request): - assert request.node.nodeid == "real.py::test_nodeid" + assert request.node.nodeid == "symlink.py::test_nodeid" """ ) symlink = testdir.tmpdir.join("symlink.py") - symlink.mksymlinkto(real) + symlink_or_skip(real, symlink) result = testdir.runpytest("-v", symlink) - result.stdout.fnmatch_lines(["real.py::test_nodeid PASSED*", "*1 passed in*"]) + result.stdout.fnmatch_lines(["symlink.py::test_nodeid PASSED*", "*1 passed in*"]) assert result.ret == 0 -@pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="symlink not available on this platform", -) def test_collect_symlink_out_of_tree(testdir): """Test collection of symlink via out-of-tree rootdir.""" sub = testdir.tmpdir.join("sub") @@ -1157,7 +1166,7 @@ def test_collect_symlink_out_of_tree(testdir): out_of_tree = testdir.tmpdir.join("out_of_tree").ensure(dir=True) symlink_to_sub = out_of_tree.join("symlink_to_sub") - symlink_to_sub.mksymlinkto(sub) + symlink_or_skip(sub, symlink_to_sub) sub.chdir() result = testdir.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub) result.stdout.fnmatch_lines( @@ -1169,7 +1178,7 @@ def test_collect_symlink_out_of_tree(testdir): assert result.ret == 0 -def test_collectignore_via_conftest(testdir, monkeypatch): +def test_collectignore_via_conftest(testdir): """collect_ignore in parent conftest skips importing child (issue #4592).""" tests = testdir.mkpydir("tests") tests.ensure("conftest.py").write("collect_ignore = ['ignore_me']") @@ -1179,7 +1188,7 @@ def test_collectignore_via_conftest(testdir, monkeypatch): ignore_me.ensure("conftest.py").write("assert 0, 'should_not_be_called'") result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED def test_collect_pkg_init_and_file_in_args(testdir): @@ -1223,22 +1232,19 @@ def test_collect_pkg_init_only(testdir): result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) -@pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="symlink not available on this platform", -) @pytest.mark.parametrize("use_pkg", (True, False)) def test_collect_sub_with_symlinks(use_pkg, testdir): + """Collection works with symlinked files and broken symlinks""" sub = testdir.mkdir("sub") if use_pkg: sub.ensure("__init__.py") - sub.ensure("test_file.py").write("def test_file(): pass") + sub.join("test_file.py").write("def test_file(): pass") # Create a broken symlink. - sub.join("test_broken.py").mksymlinkto("test_doesnotexist.py") + symlink_or_skip("test_doesnotexist.py", sub.join("test_broken.py")) # Symlink that gets collected. - sub.join("test_symlink.py").mksymlinkto("test_file.py") + symlink_or_skip("test_file.py", sub.join("test_symlink.py")) result = testdir.runpytest("-v", str(sub)) result.stdout.fnmatch_lines( @@ -1253,7 +1259,7 @@ def test_collect_sub_with_symlinks(use_pkg, testdir): def test_collector_respects_tbstyle(testdir): p1 = testdir.makepyfile("assert 0") result = testdir.runpytest(p1, "--tb=native") - assert result.ret == EXIT_INTERRUPTED + assert result.ret == ExitCode.INTERRUPTED result.stdout.fnmatch_lines( [ "*_ ERROR collecting test_collector_respects_tbstyle.py _*", @@ -1261,7 +1267,143 @@ def test_collector_respects_tbstyle(testdir): ' File "*/test_collector_respects_tbstyle.py", line 1, in <module>', " assert 0", "AssertionError: assert 0", - "*! Interrupted: 1 errors during collection !*", + "*! Interrupted: 1 error during collection !*", "*= 1 error in *", ] ) + + +def test_does_not_eagerly_collect_packages(testdir): + testdir.makepyfile("def test(): pass") + pydir = testdir.mkpydir("foopkg") + pydir.join("__init__.py").write("assert False") + result = testdir.runpytest() + assert result.ret == ExitCode.OK + + +def test_does_not_put_src_on_path(testdir): + # `src` is not on sys.path so it should not be importable + testdir.tmpdir.join("src/nope/__init__.py").ensure() + testdir.makepyfile( + "import pytest\n" + "def test():\n" + " with pytest.raises(ImportError):\n" + " import nope\n" + ) + result = testdir.runpytest() + assert result.ret == ExitCode.OK + + +def test_fscollector_from_parent(tmpdir, request): + """Ensure File.from_parent can forward custom arguments to the constructor. + + Context: https://github.com/pytest-dev/pytest-cpp/pull/47 + """ + + class MyCollector(pytest.File): + def __init__(self, fspath, parent, x): + super().__init__(fspath, parent) + self.x = x + + @classmethod + def from_parent(cls, parent, *, fspath, x): + return super().from_parent(parent=parent, fspath=fspath, x=x) + + collector = MyCollector.from_parent( + parent=request.session, fspath=tmpdir / "foo", x=10 + ) + assert collector.x == 10 + + +class TestImportModeImportlib: + def test_collect_duplicate_names(self, testdir): + """--import-mode=importlib can import modules with same names that are not in packages.""" + testdir.makepyfile( + **{ + "tests_a/test_foo.py": "def test_foo1(): pass", + "tests_b/test_foo.py": "def test_foo2(): pass", + } + ) + result = testdir.runpytest("-v", "--import-mode=importlib") + result.stdout.fnmatch_lines( + [ + "tests_a/test_foo.py::test_foo1 *", + "tests_b/test_foo.py::test_foo2 *", + "* 2 passed in *", + ] + ) + + def test_conftest(self, testdir): + """Directory containing conftest modules are not put in sys.path as a side-effect of + importing them.""" + tests_dir = testdir.tmpdir.join("tests") + testdir.makepyfile( + **{ + "tests/conftest.py": "", + "tests/test_foo.py": """ + import sys + def test_check(): + assert r"{tests_dir}" not in sys.path + """.format( + tests_dir=tests_dir + ), + } + ) + result = testdir.runpytest("-v", "--import-mode=importlib") + result.stdout.fnmatch_lines(["* 1 passed in *"]) + + def setup_conftest_and_foo(self, testdir): + """Setup a tests folder to be used to test if modules in that folder can be imported + due to side-effects of --import-mode or not.""" + testdir.makepyfile( + **{ + "tests/conftest.py": "", + "tests/foo.py": """ + def foo(): return 42 + """, + "tests/test_foo.py": """ + def test_check(): + from foo import foo + assert foo() == 42 + """, + } + ) + + def test_modules_importable_as_side_effect(self, testdir): + """In import-modes `prepend` and `append`, we are able to import modules from folders + containing conftest.py files due to the side effect of changing sys.path.""" + self.setup_conftest_and_foo(testdir) + result = testdir.runpytest("-v", "--import-mode=prepend") + result.stdout.fnmatch_lines(["* 1 passed in *"]) + + def test_modules_not_importable_as_side_effect(self, testdir): + """In import-mode `importlib`, modules in folders containing conftest.py are not + importable, as don't change sys.path or sys.modules as side effect of importing + the conftest.py file. + """ + self.setup_conftest_and_foo(testdir) + result = testdir.runpytest("-v", "--import-mode=importlib") + exc_name = ( + "ModuleNotFoundError" if sys.version_info[:2] > (3, 5) else "ImportError" + ) + result.stdout.fnmatch_lines( + [ + "*{}: No module named 'foo'".format(exc_name), + "tests?test_foo.py:2: {}".format(exc_name), + "* 1 failed in *", + ] + ) + + +def test_does_not_crash_on_error_from_decorated_function(testdir: Testdir) -> None: + """Regression test for an issue around bad exception formatting due to + assertion rewriting mangling lineno's (#4984).""" + testdir.makepyfile( + """ + @pytest.fixture + def a(): return 4 + """ + ) + result = testdir.runpytest() + # Not INTERNAL_ERROR + assert result.ret == ExitCode.INTERRUPTED diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_compat.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_compat.py index b4298d1531d..5debe87a3ed 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_compat.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_compat.py @@ -1,21 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import enum import sys +from functools import partial from functools import wraps - -import six +from typing import Union import pytest from _pytest.compat import _PytestWrapper +from _pytest.compat import assert_never +from _pytest.compat import cached_property from _pytest.compat import get_real_func from _pytest.compat import is_generator from _pytest.compat import safe_getattr from _pytest.compat import safe_isclass +from _pytest.compat import TYPE_CHECKING from _pytest.outcomes import OutcomeException +if TYPE_CHECKING: + from typing_extensions import Literal + def test_is_generator(): def zap(): @@ -29,7 +31,7 @@ def test_is_generator(): def test_real_func_loop_limit(): - class Evil(object): + class Evil: def __init__(self): self.left = 1000 @@ -62,8 +64,6 @@ def test_get_real_func(): def inner(): pass # pragma: no cover - if six.PY2: - inner.__wrapped__ = f return inner def func(): @@ -81,9 +81,16 @@ def test_get_real_func(): assert get_real_func(wrapped_func2) is wrapped_func -@pytest.mark.skipif( - sys.version_info < (3, 4), reason="asyncio available in Python 3.4+" -) +def test_get_real_func_partial(): + """Test get_real_func handles partial instances correctly""" + + def foo(x): + return x + + assert get_real_func(foo) is foo + assert get_real_func(partial(foo)) is foo + + def test_is_generator_asyncio(testdir): testdir.makepyfile( """ @@ -103,9 +110,6 @@ def test_is_generator_asyncio(testdir): result.stdout.fnmatch_lines(["*1 passed*"]) -@pytest.mark.skipif( - sys.version_info < (3, 5), reason="async syntax available in Python 3.5+" -) def test_is_generator_async_syntax(testdir): testdir.makepyfile( """ @@ -125,13 +129,40 @@ def test_is_generator_async_syntax(testdir): result.stdout.fnmatch_lines(["*1 passed*"]) -class ErrorsHelper(object): +@pytest.mark.skipif( + sys.version_info < (3, 6), reason="async gen syntax available in Python 3.6+" +) +def test_is_generator_async_gen_syntax(testdir): + testdir.makepyfile( + """ + from _pytest.compat import is_generator + def test_is_generator_py36(): + async def foo(): + yield + await foo() + + async def bar(): + yield + + assert not is_generator(foo) + assert not is_generator(bar) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 passed*"]) + + +class ErrorsHelper: + @property + def raise_baseexception(self): + raise BaseException("base exception should be raised") + @property def raise_exception(self): raise Exception("exception should be catched") @property - def raise_fail(self): + def raise_fail_outcome(self): pytest.fail("fail should be catched") @@ -140,21 +171,96 @@ def test_helper_failures(): with pytest.raises(Exception): helper.raise_exception with pytest.raises(OutcomeException): - helper.raise_fail + helper.raise_fail_outcome def test_safe_getattr(): helper = ErrorsHelper() assert safe_getattr(helper, "raise_exception", "default") == "default" - assert safe_getattr(helper, "raise_fail", "default") == "default" + assert safe_getattr(helper, "raise_fail_outcome", "default") == "default" + with pytest.raises(BaseException): + assert safe_getattr(helper, "raise_baseexception", "default") def test_safe_isclass(): assert safe_isclass(type) is True class CrappyClass(Exception): - @property + # Type ignored because it's bypassed intentionally. + @property # type: ignore def __class__(self): assert False, "Should be ignored" assert safe_isclass(CrappyClass()) is False + + +def test_cached_property() -> None: + ncalls = 0 + + class Class: + @cached_property + def prop(self) -> int: + nonlocal ncalls + ncalls += 1 + return ncalls + + c1 = Class() + assert ncalls == 0 + assert c1.prop == 1 + assert c1.prop == 1 + c2 = Class() + assert ncalls == 1 + assert c2.prop == 2 + assert c1.prop == 1 + + +def test_assert_never_union() -> None: + x = 10 # type: Union[int, str] + + if isinstance(x, int): + pass + else: + with pytest.raises(AssertionError): + assert_never(x) # type: ignore[arg-type] + + if isinstance(x, int): + pass + elif isinstance(x, str): + pass + else: + assert_never(x) + + +def test_assert_never_enum() -> None: + E = enum.Enum("E", "a b") + x = E.a # type: E + + if x is E.a: + pass + else: + with pytest.raises(AssertionError): + assert_never(x) # type: ignore[arg-type] + + if x is E.a: + pass + elif x is E.b: + pass + else: + assert_never(x) + + +def test_assert_never_literal() -> None: + x = "a" # type: Literal["a", "b"] + + if x == "a": + pass + else: + with pytest.raises(AssertionError): + assert_never(x) # type: ignore[arg-type] + + if x == "a": + pass + elif x == "b": + pass + else: + assert_never(x) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_config.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_config.py index d13f119b020..89fbbf8c957 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_config.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_config.py @@ -1,35 +1,54 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import os +import re import sys import textwrap +from typing import Dict +from typing import List +from typing import Sequence +from typing import Tuple + +import attr +import py.path import _pytest._code import pytest from _pytest.compat import importlib_metadata +from _pytest.compat import TYPE_CHECKING +from _pytest.config import _get_plugin_specs_as_list from _pytest.config import _iter_rewritable_modules +from _pytest.config import _strtobool +from _pytest.config import Config +from _pytest.config import ConftestImportFailure +from _pytest.config import ExitCode +from _pytest.config import parse_warning_filter from _pytest.config.exceptions import UsageError from _pytest.config.findpaths import determine_setup from _pytest.config.findpaths import get_common_ancestor -from _pytest.config.findpaths import getcfg -from _pytest.main import EXIT_INTERRUPTED -from _pytest.main import EXIT_NOTESTSCOLLECTED -from _pytest.main import EXIT_OK -from _pytest.main import EXIT_TESTSFAILED -from _pytest.main import EXIT_USAGEERROR +from _pytest.config.findpaths import locate_config +from _pytest.monkeypatch import MonkeyPatch from _pytest.pathlib import Path +from _pytest.pytester import Testdir +if TYPE_CHECKING: + from typing import Type -class TestParseIni(object): + +class TestParseIni: @pytest.mark.parametrize( "section, filename", [("pytest", "pytest.ini"), ("tool:pytest", "setup.cfg")] ) - def test_getcfg_and_config(self, testdir, tmpdir, section, filename): - sub = tmpdir.mkdir("sub") - sub.chdir() - tmpdir.join(filename).write( + def test_getcfg_and_config( + self, + testdir: Testdir, + tmp_path: Path, + section: str, + filename: str, + monkeypatch: MonkeyPatch, + ) -> None: + sub = tmp_path / "sub" + sub.mkdir() + monkeypatch.chdir(sub) + (tmp_path / filename).write_text( textwrap.dedent( """\ [{section}] @@ -37,17 +56,14 @@ class TestParseIni(object): """.format( section=section ) - ) + ), + encoding="utf-8", ) - rootdir, inifile, cfg = getcfg([sub]) + _, _, cfg = locate_config([sub]) assert cfg["name"] == "value" - config = testdir.parseconfigure(sub) + config = testdir.parseconfigure(str(sub)) assert config.inicfg["name"] == "value" - def test_getcfg_empty_path(self): - """correctly handle zero length arguments (a la pytest '')""" - getcfg([""]) - def test_setupcfg_uses_toolpytest_with_pytest(self, testdir): p1 = testdir.makepyfile("def test(): pass") testdir.makefile( @@ -61,7 +77,7 @@ class TestParseIni(object): % p1.basename, ) result = testdir.runpytest() - result.stdout.fnmatch_lines(["*, inifile: setup.cfg, *", "* 1 passed in *"]) + result.stdout.fnmatch_lines(["*, configfile: setup.cfg, *", "* 1 passed in *"]) assert result.ret == 0 def test_append_parse_args(self, testdir, tmpdir, monkeypatch): @@ -85,12 +101,14 @@ class TestParseIni(object): ".ini", tox=""" [pytest] - minversion=9.0 + minversion=999.0 """, ) result = testdir.runpytest() assert result.ret != 0 - result.stderr.fnmatch_lines(["*tox.ini:2*requires*9.0*actual*"]) + result.stderr.fnmatch_lines( + ["*tox.ini: 'minversion' requires pytest-999.0, actual pytest-*"] + ) @pytest.mark.parametrize( "section, name", @@ -110,6 +128,16 @@ class TestParseIni(object): config = testdir.parseconfig() assert config.getini("minversion") == "1.0" + def test_pyproject_toml(self, testdir): + testdir.makepyprojecttoml( + """ + [tool.pytest.ini_options] + minversion = "1.0" + """ + ) + config = testdir.parseconfig() + assert config.getini("minversion") == "1.0" + def test_toxini_before_lower_pytestini(self, testdir): sub = testdir.tmpdir.mkdir("sub") sub.join("tox.ini").write( @@ -150,8 +178,271 @@ class TestParseIni(object): result = testdir.inline_run("--confcutdir=.") assert result.ret == 0 + @pytest.mark.parametrize( + "ini_file_text, invalid_keys, warning_output, exception_text", + [ + pytest.param( + """ + [pytest] + unknown_ini = value1 + another_unknown_ini = value2 + """, + ["unknown_ini", "another_unknown_ini"], + [ + "=*= warnings summary =*=", + "*PytestConfigWarning:*Unknown config option: another_unknown_ini", + "*PytestConfigWarning:*Unknown config option: unknown_ini", + ], + "Unknown config option: another_unknown_ini", + id="2-unknowns", + ), + pytest.param( + """ + [pytest] + unknown_ini = value1 + minversion = 5.0.0 + """, + ["unknown_ini"], + [ + "=*= warnings summary =*=", + "*PytestConfigWarning:*Unknown config option: unknown_ini", + ], + "Unknown config option: unknown_ini", + id="1-unknown", + ), + pytest.param( + """ + [some_other_header] + unknown_ini = value1 + [pytest] + minversion = 5.0.0 + """, + [], + [], + "", + id="unknown-in-other-header", + ), + pytest.param( + """ + [pytest] + minversion = 5.0.0 + """, + [], + [], + "", + id="no-unknowns", + ), + pytest.param( + """ + [pytest] + conftest_ini_key = 1 + """, + [], + [], + "", + id="1-known", + ), + ], + ) + @pytest.mark.filterwarnings("default") + def test_invalid_config_options( + self, testdir, ini_file_text, invalid_keys, warning_output, exception_text + ): + testdir.makeconftest( + """ + def pytest_addoption(parser): + parser.addini("conftest_ini_key", "") + """ + ) + testdir.makepyfile("def test(): pass") + testdir.makeini(ini_file_text) + + config = testdir.parseconfig() + assert sorted(config._get_unknown_ini_keys()) == sorted(invalid_keys) + + result = testdir.runpytest() + result.stdout.fnmatch_lines(warning_output) + + result = testdir.runpytest("--strict-config") + if exception_text: + result.stderr.fnmatch_lines("ERROR: " + exception_text) + assert result.ret == pytest.ExitCode.USAGE_ERROR + else: + result.stderr.no_fnmatch_line(exception_text) + assert result.ret == pytest.ExitCode.OK + + @pytest.mark.filterwarnings("default") + def test_silence_unknown_key_warning(self, testdir: Testdir) -> None: + """Unknown config key warnings can be silenced using filterwarnings (#7620)""" + testdir.makeini( + """ + [pytest] + filterwarnings = + ignore:Unknown config option:pytest.PytestConfigWarning + foobar=1 + """ + ) + result = testdir.runpytest() + result.stdout.no_fnmatch_line("*PytestConfigWarning*") -class TestConfigCmdlineParsing(object): + @pytest.mark.filterwarnings("default") + def test_disable_warnings_plugin_disables_config_warnings( + self, testdir: Testdir + ) -> None: + """Disabling 'warnings' plugin also disables config time warnings""" + testdir.makeconftest( + """ + import pytest + def pytest_configure(config): + config.issue_config_time_warning( + pytest.PytestConfigWarning("custom config warning"), + stacklevel=2, + ) + """ + ) + result = testdir.runpytest("-pno:warnings") + result.stdout.no_fnmatch_line("*PytestConfigWarning*") + + @pytest.mark.parametrize( + "ini_file_text, exception_text", + [ + pytest.param( + """ + [pytest] + required_plugins = a z + """, + "Missing required plugins: a, z", + id="2-missing", + ), + pytest.param( + """ + [pytest] + required_plugins = a z myplugin + """, + "Missing required plugins: a, z", + id="2-missing-1-ok", + ), + pytest.param( + """ + [pytest] + required_plugins = myplugin + """, + None, + id="1-ok", + ), + pytest.param( + """ + [pytest] + required_plugins = myplugin==1.5 + """, + None, + id="1-ok-pin-exact", + ), + pytest.param( + """ + [pytest] + required_plugins = myplugin>1.0,<2.0 + """, + None, + id="1-ok-pin-loose", + ), + pytest.param( + """ + [pytest] + required_plugins = pyplugin==1.6 + """, + "Missing required plugins: pyplugin==1.6", + id="missing-version", + ), + pytest.param( + """ + [pytest] + required_plugins = pyplugin==1.6 other==1.0 + """, + "Missing required plugins: other==1.0, pyplugin==1.6", + id="missing-versions", + ), + pytest.param( + """ + [some_other_header] + required_plugins = wont be triggered + [pytest] + """, + None, + id="invalid-header", + ), + ], + ) + def test_missing_required_plugins( + self, testdir, monkeypatch, ini_file_text, exception_text + ): + """Check 'required_plugins' option with various settings. + + This test installs a mock "myplugin-1.5" which is used in the parametrized test cases. + """ + + @attr.s + class DummyEntryPoint: + name = attr.ib() + module = attr.ib() + group = "pytest11" + + def load(self): + __import__(self.module) + return sys.modules[self.module] + + entry_points = [ + DummyEntryPoint("myplugin1", "myplugin1_module"), + ] + + @attr.s + class DummyDist: + entry_points = attr.ib() + files = () + version = "1.5" + + @property + def metadata(self): + return {"name": "myplugin"} + + def my_dists(): + return [DummyDist(entry_points)] + + testdir.makepyfile(myplugin1_module="# my plugin module") + testdir.syspathinsert() + + monkeypatch.setattr(importlib_metadata, "distributions", my_dists) + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) + + testdir.makeini(ini_file_text) + + if exception_text: + with pytest.raises(pytest.UsageError, match=exception_text): + testdir.parseconfig() + else: + testdir.parseconfig() + + def test_early_config_cmdline(self, testdir, monkeypatch): + """early_config contains options registered by third-party plugins. + + This is a regression involving pytest-cov (and possibly others) introduced in #7700. + """ + testdir.makepyfile( + myplugin=""" + def pytest_addoption(parser): + parser.addoption('--foo', default=None, dest='foo') + + def pytest_load_initial_conftests(early_config, parser, args): + assert early_config.known_args_namespace.foo == "1" + """ + ) + monkeypatch.setenv("PYTEST_PLUGINS", "myplugin") + testdir.syspathinsert() + result = testdir.runpytest("--foo=1") + result.stdout.fnmatch_lines("* no tests ran in *") + + +class TestConfigCmdlineParsing: def test_parsing_again_fails(self, testdir): config = testdir.parseconfig() pytest.raises(AssertionError, lambda: config.parse([])) @@ -189,6 +480,18 @@ class TestConfigCmdlineParsing(object): config = testdir.parseconfig("-c", "custom_tool_pytest_section.cfg") assert config.getini("custom") == "1" + testdir.makefile( + ".toml", + custom=""" + [tool.pytest.ini_options] + custom = 1 + value = [ + ] # this is here on purpose, as it makes this an invalid '.ini' file + """, + ) + config = testdir.parseconfig("-c", "custom.toml") + assert config.getini("custom") == "1" + def test_absolute_win32_path(self, testdir): temp_ini_file = testdir.makefile( ".ini", @@ -201,13 +504,13 @@ class TestConfigCmdlineParsing(object): temp_ini_file = normpath(str(temp_ini_file)) ret = pytest.main(["-c", temp_ini_file]) - assert ret == _pytest.main.EXIT_OK + assert ret == ExitCode.OK -class TestConfigAPI(object): - def test_config_trace(self, testdir): +class TestConfigAPI: + def test_config_trace(self, testdir) -> None: config = testdir.parseconfig() - values = [] + values = [] # type: List[str] config.trace.root.setwriter(values.append) config.trace("hello") assert len(values) == 1 @@ -225,12 +528,9 @@ class TestConfigAPI(object): assert config.getoption(x) == "this" pytest.raises(ValueError, config.getoption, "qweqwe") - @pytest.mark.skipif("sys.version_info[0] < 3") def test_config_getoption_unicode(self, testdir): testdir.makeconftest( """ - from __future__ import unicode_literals - def pytest_addoption(parser): parser.addoption('--hello', type=str) """ @@ -291,7 +591,7 @@ class TestConfigAPI(object): assert val == "hello" pytest.raises(ValueError, config.getini, "other") - def test_addini_pathlist(self, testdir): + def make_conftest_for_pathlist(self, testdir): testdir.makeconftest( """ def pytest_addoption(parser): @@ -299,20 +599,36 @@ class TestConfigAPI(object): parser.addini("abc", "abc value") """ ) + + def test_addini_pathlist_ini_files(self, testdir): + self.make_conftest_for_pathlist(testdir) p = testdir.makeini( """ [pytest] paths=hello world/sub.py """ ) + self.check_config_pathlist(testdir, p) + + def test_addini_pathlist_pyproject_toml(self, testdir): + self.make_conftest_for_pathlist(testdir) + p = testdir.makepyprojecttoml( + """ + [tool.pytest.ini_options] + paths=["hello", "world/sub.py"] + """ + ) + self.check_config_pathlist(testdir, p) + + def check_config_pathlist(self, testdir, config_path): config = testdir.parseconfig() values = config.getini("paths") assert len(values) == 2 - assert values[0] == p.dirpath("hello") - assert values[1] == p.dirpath("world/sub.py") + assert values[0] == config_path.dirpath("hello") + assert values[1] == config_path.dirpath("world/sub.py") pytest.raises(ValueError, config.getini, "other") - def test_addini_args(self, testdir): + def make_conftest_for_args(self, testdir): testdir.makeconftest( """ def pytest_addoption(parser): @@ -320,20 +636,35 @@ class TestConfigAPI(object): parser.addini("a2", "", "args", default="1 2 3".split()) """ ) + + def test_addini_args_ini_files(self, testdir): + self.make_conftest_for_args(testdir) testdir.makeini( """ [pytest] args=123 "123 hello" "this" - """ + """ ) + self.check_config_args(testdir) + + def test_addini_args_pyproject_toml(self, testdir): + self.make_conftest_for_args(testdir) + testdir.makepyprojecttoml( + """ + [tool.pytest.ini_options] + args = ["123", "123 hello", "this"] + """ + ) + self.check_config_args(testdir) + + def check_config_args(self, testdir): config = testdir.parseconfig() values = config.getini("args") - assert len(values) == 3 assert values == ["123", "123 hello", "this"] values = config.getini("a2") assert values == list("123") - def test_addini_linelist(self, testdir): + def make_conftest_for_linelist(self, testdir): testdir.makeconftest( """ def pytest_addoption(parser): @@ -341,6 +672,9 @@ class TestConfigAPI(object): parser.addini("a2", "", "linelist") """ ) + + def test_addini_linelist_ini_files(self, testdir): + self.make_conftest_for_linelist(testdir) testdir.makeini( """ [pytest] @@ -348,6 +682,19 @@ class TestConfigAPI(object): second line """ ) + self.check_config_linelist(testdir) + + def test_addini_linelist_pprojecttoml(self, testdir): + self.make_conftest_for_linelist(testdir) + testdir.makepyprojecttoml( + """ + [tool.pytest.ini_options] + xy = ["123 345", "second line"] + """ + ) + self.check_config_linelist(testdir) + + def check_config_linelist(self, testdir): config = testdir.parseconfig() values = config.getini("xy") assert len(values) == 2 @@ -418,11 +765,12 @@ class TestConfigAPI(object): def test_confcutdir_check_isdir(self, testdir): """Give an error if --confcutdir is not a valid directory (#2078)""" - with pytest.raises(pytest.UsageError): + exp_match = r"^--confcutdir must be a directory, given: " + with pytest.raises(pytest.UsageError, match=exp_match): testdir.parseconfig( "--confcutdir", testdir.tmpdir.join("file").ensure(file=1) ) - with pytest.raises(pytest.UsageError): + with pytest.raises(pytest.UsageError, match=exp_match): testdir.parseconfig("--confcutdir", testdir.tmpdir.join("inexistant")) config = testdir.parseconfig( "--confcutdir", testdir.tmpdir.join("dir").ensure(dir=1) @@ -449,10 +797,8 @@ class TestConfigAPI(object): assert list(_iter_rewritable_modules(names)) == expected -class TestConfigFromdictargs(object): +class TestConfigFromdictargs: def test_basic_behavior(self, _sys_snapshot): - from _pytest.config import Config - option_dict = {"verbose": 444, "foo": "bar", "capture": "no"} args = ["a", "b"] @@ -464,16 +810,14 @@ class TestConfigFromdictargs(object): assert config.option.capture == "no" assert config.args == args - def test_origargs(self, _sys_snapshot): + def test_invocation_params_args(self, _sys_snapshot) -> None: """Show that fromdictargs can handle args in their "orig" format""" - from _pytest.config import Config - - option_dict = {} + option_dict = {} # type: Dict[str, object] args = ["-vvvv", "-s", "a", "b"] config = Config.fromdictargs(option_dict, args) assert config.args == ["a", "b"] - assert config._origargs == args + assert config.invocation_params.args == tuple(args) assert config.option.verbose == 4 assert config.option.capture == "no" @@ -487,8 +831,6 @@ class TestConfigFromdictargs(object): ) ) - from _pytest.config import Config - inifile = "../../foo/bar.ini" option_dict = {"inifilename": inifile, "capture": "no"} @@ -504,19 +846,20 @@ class TestConfigFromdictargs(object): ) with cwd.ensure(dir=True).as_cwd(): config = Config.fromdictargs(option_dict, ()) + inipath = py.path.local(inifile) assert config.args == [str(cwd)] assert config.option.inifilename == inifile assert config.option.capture == "no" # this indicates this is the file used for getting configuration values - assert config.inifile == inifile + assert config.inifile == inipath assert config.inicfg.get("name") == "value" assert config.inicfg.get("should_not_be_set") is None -def test_options_on_small_file_do_not_blow_up(testdir): - def runfiletest(opts): +def test_options_on_small_file_do_not_blow_up(testdir) -> None: + def runfiletest(opts: Sequence[str]) -> None: reprec = testdir.inline_run(*opts) passed, skipped, failed = reprec.countoutcomes() assert failed == 2 @@ -529,36 +872,34 @@ def test_options_on_small_file_do_not_blow_up(testdir): """ ) - for opts in ( - [], - ["-l"], - ["-s"], - ["--tb=no"], - ["--tb=short"], - ["--tb=long"], - ["--fulltrace"], - ["--traceconfig"], - ["-v"], - ["-v", "-v"], - ): - runfiletest(opts + [path]) + runfiletest([path]) + runfiletest(["-l", path]) + runfiletest(["-s", path]) + runfiletest(["--tb=no", path]) + runfiletest(["--tb=short", path]) + runfiletest(["--tb=long", path]) + runfiletest(["--fulltrace", path]) + runfiletest(["--traceconfig", path]) + runfiletest(["-v", path]) + runfiletest(["-v", "-v", path]) def test_preparse_ordering_with_setuptools(testdir, monkeypatch): monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) - class EntryPoint(object): + class EntryPoint: name = "mytestplugin" group = "pytest11" def load(self): - class PseudoPlugin(object): + class PseudoPlugin: x = 42 return PseudoPlugin() - class Dist(object): + class Dist: files = () + metadata = {"name": "foo"} entry_points = (EntryPoint(),) def my_dists(): @@ -579,16 +920,17 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch): def test_setuptools_importerror_issue1479(testdir, monkeypatch): monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) - class DummyEntryPoint(object): + class DummyEntryPoint: name = "mytestplugin" group = "pytest11" def load(self): raise ImportError("Don't hide me!") - class Distribution(object): + class Distribution: version = "1.0" files = ("foo.txt",) + metadata = {"name": "foo"} entry_points = (DummyEntryPoint(),) def distributions(): @@ -613,6 +955,7 @@ def test_importlib_metadata_broken_distribution(testdir, monkeypatch): class Distribution: version = "1.0" files = None + metadata = {"name": "foo"} entry_points = (DummyEntryPoint(),) def distributions(): @@ -628,16 +971,17 @@ def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block plugin_module_placeholder = object() - class DummyEntryPoint(object): + class DummyEntryPoint: name = "mytestplugin" group = "pytest11" def load(self): return plugin_module_placeholder - class Distribution(object): + class Distribution: version = "1.0" files = ("foo.txt",) + metadata = {"name": "foo"} entry_points = (DummyEntryPoint(),) def distributions(): @@ -660,7 +1004,7 @@ def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block "parse_args,should_load", [(("-p", "mytestplugin"), True), ((), False)] ) def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load): - class DummyEntryPoint(object): + class DummyEntryPoint: project_name = name = "mytestplugin" group = "pytest11" version = "1.0" @@ -668,13 +1012,21 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load): def load(self): return sys.modules[self.name] - class Distribution(object): + class Distribution: + metadata = {"name": "foo"} entry_points = (DummyEntryPoint(),) files = () - class PseudoPlugin(object): + class PseudoPlugin: x = 42 + attrs_used = [] + + def __getattr__(self, name): + assert name == "__loader__" + self.attrs_used.append(name) + return object() + def distributions(): return (Distribution(),) @@ -684,6 +1036,36 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load): config = testdir.parseconfig(*parse_args) has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None assert has_loaded == should_load + if should_load: + assert PseudoPlugin.attrs_used == ["__loader__"] + else: + assert PseudoPlugin.attrs_used == [] + + +def test_plugin_loading_order(testdir): + """Test order of plugin loading with `-p`.""" + p1 = testdir.makepyfile( + """ + def test_terminal_plugin(request): + import myplugin + assert myplugin.terminal_plugin == [False, True] + """, + **{ + "myplugin": """ + terminal_plugin = [] + + def pytest_configure(config): + terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter"))) + + def pytest_sessionstart(session): + config = session.config + terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter"))) + """ + }, + ) + testdir.syspathinsert() + result = testdir.runpytest("-p", "myplugin", str(p1)) + assert result.ret == 0 def test_cmdline_processargs_simple(testdir): @@ -698,8 +1080,8 @@ def test_cmdline_processargs_simple(testdir): def test_invalid_options_show_extra_information(testdir): - """display extra information when pytest exits due to unrecognized - options in the command-line""" + """Display extra information when pytest exits due to unrecognized + options in the command-line.""" testdir.makeini( """ [pytest] @@ -744,10 +1126,9 @@ def test_consider_args_after_options_for_rootdir(testdir, args): result.stdout.fnmatch_lines(["*rootdir: *myroot"]) -@pytest.mark.skipif("sys.platform == 'win32'") def test_toolongargs_issue224(testdir): result = testdir.runpytest("-m", "hello" * 500) - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED def test_config_in_subdirectory_colon_command_line_issue2148(testdir): @@ -758,7 +1139,7 @@ def test_config_in_subdirectory_colon_command_line_issue2148(testdir): testdir.makefile( ".ini", - **{"pytest": "[pytest]\nfoo = root", "subdir/pytest": "[pytest]\nfoo = subdir"} + **{"pytest": "[pytest]\nfoo = root", "subdir/pytest": "[pytest]\nfoo = subdir"}, ) testdir.makepyfile( @@ -781,36 +1162,36 @@ def test_notify_exception(testdir, capfd): with pytest.raises(ValueError) as excinfo: raise ValueError(1) config.notify_exception(excinfo, config.option) - out, err = capfd.readouterr() + _, err = capfd.readouterr() assert "ValueError" in err - class A(object): - def pytest_internalerror(self, excrepr): + class A: + def pytest_internalerror(self): return True config.pluginmanager.register(A()) config.notify_exception(excinfo, config.option) - out, err = capfd.readouterr() + _, err = capfd.readouterr() assert not err config = testdir.parseconfig("-p", "no:terminal") with pytest.raises(ValueError) as excinfo: raise ValueError(1) config.notify_exception(excinfo, config.option) - out, err = capfd.readouterr() + _, err = capfd.readouterr() assert "ValueError" in err def test_no_terminal_discovery_error(testdir): testdir.makepyfile("raise TypeError('oops!')") result = testdir.runpytest("-p", "no:terminal", "--collect-only") - assert result.ret == EXIT_INTERRUPTED + assert result.ret == ExitCode.INTERRUPTED -def test_load_initial_conftest_last_ordering(testdir, _config_for_test): +def test_load_initial_conftest_last_ordering(_config_for_test): pm = _config_for_test.pluginmanager - class My(object): + class My: def pytest_load_initial_conftests(self): pass @@ -818,17 +1199,21 @@ def test_load_initial_conftest_last_ordering(testdir, _config_for_test): pm.register(m) hc = pm.hook.pytest_load_initial_conftests values = hc._nonwrappers + hc._wrappers - expected = ["_pytest.config", "test_config", "_pytest.capture"] + expected = ["_pytest.config", m.__module__, "_pytest.capture", "_pytest.warnings"] assert [x.function.__module__ for x in values] == expected -def test_get_plugin_specs_as_list(): - from _pytest.config import _get_plugin_specs_as_list +def test_get_plugin_specs_as_list() -> None: + def exp_match(val: object) -> str: + return ( + "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %s" + % re.escape(repr(val)) + ) - with pytest.raises(pytest.UsageError): - _get_plugin_specs_as_list({"foo"}) - with pytest.raises(pytest.UsageError): - _get_plugin_specs_as_list(dict()) + with pytest.raises(pytest.UsageError, match=exp_match({"foo"})): + _get_plugin_specs_as_list({"foo"}) # type: ignore[arg-type] + with pytest.raises(pytest.UsageError, match=exp_match({})): + _get_plugin_specs_as_list(dict()) # type: ignore[arg-type] assert _get_plugin_specs_as_list(None) == [] assert _get_plugin_specs_as_list("") == [] @@ -848,73 +1233,165 @@ def test_collect_pytest_prefix_bug_integration(testdir): def test_collect_pytest_prefix_bug(pytestconfig): """Ensure we collect only actual functions from conftest files (#3775)""" - class Dummy(object): - class pytest_something(object): + class Dummy: + class pytest_something: pass pm = pytestconfig.pluginmanager assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None -class TestRootdir(object): - def test_simple_noini(self, tmpdir): - assert get_common_ancestor([tmpdir]) == tmpdir - a = tmpdir.mkdir("a") - assert get_common_ancestor([a, tmpdir]) == tmpdir - assert get_common_ancestor([tmpdir, a]) == tmpdir - with tmpdir.as_cwd(): - assert get_common_ancestor([]) == tmpdir - no_path = tmpdir.join("does-not-exist") - assert get_common_ancestor([no_path]) == tmpdir - assert get_common_ancestor([no_path.join("a")]) == tmpdir +class TestRootdir: + def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + assert get_common_ancestor([tmp_path]) == tmp_path + a = tmp_path / "a" + a.mkdir() + assert get_common_ancestor([a, tmp_path]) == tmp_path + assert get_common_ancestor([tmp_path, a]) == tmp_path + monkeypatch.chdir(tmp_path) + assert get_common_ancestor([]) == tmp_path + no_path = tmp_path / "does-not-exist" + assert get_common_ancestor([no_path]) == tmp_path + assert get_common_ancestor([no_path / "a"]) == tmp_path - @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) - def test_with_ini(self, tmpdir, name): - inifile = tmpdir.join(name) - inifile.write("[pytest]\n" if name != "setup.cfg" else "[tool:pytest]\n") - - a = tmpdir.mkdir("a") - b = a.mkdir("b") - for args in ([tmpdir], [a], [b]): - rootdir, inifile, inicfg = determine_setup(None, args) - assert rootdir == tmpdir - assert inifile == inifile - rootdir, inifile, inicfg = determine_setup(None, [b, a]) - assert rootdir == tmpdir - assert inifile == inifile - - @pytest.mark.parametrize("name", "setup.cfg tox.ini".split()) - def test_pytestini_overrides_empty_other(self, tmpdir, name): - inifile = tmpdir.ensure("pytest.ini") - a = tmpdir.mkdir("a") - a.ensure(name) - rootdir, inifile, inicfg = determine_setup(None, [a]) - assert rootdir == tmpdir - assert inifile == inifile - - def test_setuppy_fallback(self, tmpdir): - a = tmpdir.mkdir("a") - a.ensure("setup.cfg") - tmpdir.ensure("setup.py") - rootdir, inifile, inicfg = determine_setup(None, [a]) - assert rootdir == tmpdir - assert inifile is None + @pytest.mark.parametrize( + "name, contents", + [ + pytest.param("pytest.ini", "[pytest]\nx=10", id="pytest.ini"), + pytest.param( + "pyproject.toml", "[tool.pytest.ini_options]\nx=10", id="pyproject.toml" + ), + pytest.param("tox.ini", "[pytest]\nx=10", id="tox.ini"), + pytest.param("setup.cfg", "[tool:pytest]\nx=10", id="setup.cfg"), + ], + ) + def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None: + inipath = tmp_path / name + inipath.write_text(contents, "utf-8") + + a = tmp_path / "a" + a.mkdir() + b = a / "b" + b.mkdir() + for args in ([str(tmp_path)], [str(a)], [str(b)]): + rootpath, parsed_inipath, _ = determine_setup(None, args) + assert rootpath == tmp_path + assert parsed_inipath == inipath + rootpath, parsed_inipath, ini_config = determine_setup(None, [str(b), str(a)]) + assert rootpath == tmp_path + assert parsed_inipath == inipath + assert ini_config == {"x": "10"} + + @pytest.mark.parametrize("name", ["setup.cfg", "tox.ini"]) + def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> None: + inipath = tmp_path / "pytest.ini" + inipath.touch() + a = tmp_path / "a" + a.mkdir() + (a / name).touch() + rootpath, parsed_inipath, _ = determine_setup(None, [str(a)]) + assert rootpath == tmp_path + assert parsed_inipath == inipath + + def test_setuppy_fallback(self, tmp_path: Path) -> None: + a = tmp_path / "a" + a.mkdir() + (a / "setup.cfg").touch() + (tmp_path / "setup.py").touch() + rootpath, inipath, inicfg = determine_setup(None, [str(a)]) + assert rootpath == tmp_path + assert inipath is None assert inicfg == {} - def test_nothing(self, tmpdir, monkeypatch): - monkeypatch.chdir(str(tmpdir)) - rootdir, inifile, inicfg = determine_setup(None, [tmpdir]) - assert rootdir == tmpdir - assert inifile is None + def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + monkeypatch.chdir(tmp_path) + rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)]) + assert rootpath == tmp_path + assert inipath is None assert inicfg == {} - def test_with_specific_inifile(self, tmpdir): - inifile = tmpdir.ensure("pytest.ini") - rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir]) - assert rootdir == tmpdir + @pytest.mark.parametrize( + "name, contents", + [ + # pytest.param("pytest.ini", "[pytest]\nx=10", id="pytest.ini"), + pytest.param( + "pyproject.toml", "[tool.pytest.ini_options]\nx=10", id="pyproject.toml" + ), + # pytest.param("tox.ini", "[pytest]\nx=10", id="tox.ini"), + # pytest.param("setup.cfg", "[tool:pytest]\nx=10", id="setup.cfg"), + ], + ) + def test_with_specific_inifile( + self, tmp_path: Path, name: str, contents: str + ) -> None: + p = tmp_path / name + p.touch() + p.write_text(contents, "utf-8") + rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)]) + assert rootpath == tmp_path + assert inipath == p + assert ini_config == {"x": "10"} + + def test_with_arg_outside_cwd_without_inifile( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: + monkeypatch.chdir(tmp_path) + a = tmp_path / "a" + a.mkdir() + b = tmp_path / "b" + b.mkdir() + rootpath, inifile, _ = determine_setup(None, [str(a), str(b)]) + assert rootpath == tmp_path + assert inifile is None + def test_with_arg_outside_cwd_with_inifile(self, tmp_path: Path) -> None: + a = tmp_path / "a" + a.mkdir() + b = tmp_path / "b" + b.mkdir() + inipath = a / "pytest.ini" + inipath.touch() + rootpath, parsed_inipath, _ = determine_setup(None, [str(a), str(b)]) + assert rootpath == a + assert inipath == parsed_inipath -class TestOverrideIniArgs(object): + @pytest.mark.parametrize("dirs", ([], ["does-not-exist"], ["a/does-not-exist"])) + def test_with_non_dir_arg( + self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: + monkeypatch.chdir(tmp_path) + rootpath, inipath, _ = determine_setup(None, dirs) + assert rootpath == tmp_path + assert inipath is None + + def test_with_existing_file_in_subdir( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: + a = tmp_path / "a" + a.mkdir() + (a / "exists").touch() + monkeypatch.chdir(tmp_path) + rootpath, inipath, _ = determine_setup(None, ["a/exist"]) + assert rootpath == tmp_path + assert inipath is None + + def test_with_config_also_in_parent_directory( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: + """Regression test for #7807.""" + (tmp_path / "setup.cfg").write_text("[tool:pytest]\n", "utf-8") + (tmp_path / "myproject").mkdir() + (tmp_path / "myproject" / "setup.cfg").write_text("[tool:pytest]\n", "utf-8") + (tmp_path / "myproject" / "tests").mkdir() + monkeypatch.chdir(tmp_path / "myproject") + + rootpath, inipath, _ = determine_setup(None, ["tests/"]) + + assert rootpath == tmp_path / "myproject" + assert inipath == tmp_path / "myproject" / "setup.cfg" + + +class TestOverrideIniArgs: @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) def test_override_ini_names(self, testdir, name): section = "[pytest]" if name != "setup.cfg" else "[tool:pytest]" @@ -1027,8 +1504,12 @@ class TestOverrideIniArgs(object): xdist_strict=False """ ) - result = testdir.runpytest("--override-ini", "xdist_strict True", "-s") - result.stderr.fnmatch_lines(["*ERROR* *expects option=value*"]) + result = testdir.runpytest("--override-ini", "xdist_strict", "True") + result.stderr.fnmatch_lines( + [ + "ERROR: -o/--override-ini expects option=value style (got: 'xdist_strict').", + ] + ) @pytest.mark.parametrize("with_ini", [True, False]) def test_override_ini_handled_asap(self, testdir, with_ini): @@ -1049,37 +1530,6 @@ class TestOverrideIniArgs(object): result = testdir.runpytest("--override-ini", "python_files=unittest_*.py") result.stdout.fnmatch_lines(["*1 passed in*"]) - def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch): - monkeypatch.chdir(str(tmpdir)) - a = tmpdir.mkdir("a") - b = tmpdir.mkdir("b") - rootdir, inifile, inicfg = determine_setup(None, [a, b]) - assert rootdir == tmpdir - assert inifile is None - - def test_with_arg_outside_cwd_with_inifile(self, tmpdir): - a = tmpdir.mkdir("a") - b = tmpdir.mkdir("b") - inifile = a.ensure("pytest.ini") - rootdir, parsed_inifile, inicfg = determine_setup(None, [a, b]) - assert rootdir == a - assert inifile == parsed_inifile - - @pytest.mark.parametrize("dirs", ([], ["does-not-exist"], ["a/does-not-exist"])) - def test_with_non_dir_arg(self, dirs, tmpdir): - with tmpdir.ensure(dir=True).as_cwd(): - rootdir, inifile, inicfg = determine_setup(None, dirs) - assert rootdir == tmpdir - assert inifile is None - - def test_with_existing_file_in_subdir(self, tmpdir): - a = tmpdir.mkdir("a") - a.ensure("exist") - with tmpdir.as_cwd(): - rootdir, inifile, inicfg = determine_setup(None, ["a/exist"]) - assert rootdir == tmpdir - assert inifile is None - def test_addopts_before_initini(self, monkeypatch, _config_for_test, _sys_snapshot): cache_dir = ".custom_cache" monkeypatch.setenv("PYTEST_ADDOPTS", "-o cache_dir=%s" % cache_dir) @@ -1099,7 +1549,7 @@ class TestOverrideIniArgs(object): ) def test_addopts_from_ini_not_concatenated(self, testdir): - """addopts from ini should not take values from normal args (#4265).""" + """`addopts` from ini should not take values from normal args (#4265).""" testdir.makeini( """ [pytest] @@ -1113,7 +1563,7 @@ class TestOverrideIniArgs(object): % (testdir.request.config._parser.optparser.prog,) ] ) - assert result.ret == _pytest.main.EXIT_USAGEERROR + assert result.ret == _pytest.config.ExitCode.USAGE_ERROR def test_override_ini_does_not_contain_paths(self, _config_for_test, _sys_snapshot): """Check that -o no longer swallows all options after it (#3103)""" @@ -1121,7 +1571,7 @@ class TestOverrideIniArgs(object): config._preparse(["-o", "cache_dir=/cache", "/some/test/path"]) assert config._override_ini == ["cache_dir=/cache"] - def test_multiple_override_ini_options(self, testdir, request): + def test_multiple_override_ini_options(self, testdir): """Ensure a file path following a '-o' option does not generate an error (#3103)""" testdir.makepyfile( **{ @@ -1202,13 +1652,26 @@ def test_help_and_version_after_argument_error(testdir): ) # Does not display full/default help. assert "to see available markers type: pytest --markers" not in result.stdout.lines - assert result.ret == EXIT_USAGEERROR + assert result.ret == ExitCode.USAGE_ERROR result = testdir.runpytest("--version") - result.stderr.fnmatch_lines( - ["*pytest*{}*imported from*".format(pytest.__version__)] - ) - assert result.ret == EXIT_USAGEERROR + result.stderr.fnmatch_lines(["pytest {}".format(pytest.__version__)]) + assert result.ret == ExitCode.USAGE_ERROR + + +def test_help_formatter_uses_py_get_terminal_width(monkeypatch): + from _pytest.config.argparsing import DropShorterLongHelpFormatter + + monkeypatch.setenv("COLUMNS", "90") + formatter = DropShorterLongHelpFormatter("prog") + assert formatter._width == 90 + + monkeypatch.setattr("_pytest._io.get_terminal_width", lambda: 160) + formatter = DropShorterLongHelpFormatter("prog") + assert formatter._width == 160 + + formatter = DropShorterLongHelpFormatter("prog", width=42) + assert formatter._width == 42 def test_config_does_not_load_blocked_plugin_from_args(testdir): @@ -1216,17 +1679,17 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir): p = testdir.makepyfile("def test(capfd): pass") result = testdir.runpytest(str(p), "-pno:capture") result.stdout.fnmatch_lines(["E fixture 'capfd' not found"]) - assert result.ret == EXIT_TESTSFAILED + assert result.ret == ExitCode.TESTS_FAILED result = testdir.runpytest(str(p), "-pno:capture", "-s") result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"]) - assert result.ret == EXIT_USAGEERROR + assert result.ret == ExitCode.USAGE_ERROR def test_invocation_args(testdir): """Ensure that Config.invocation_* arguments are correctly defined""" - class DummyPlugin(object): + class DummyPlugin: pass p = testdir.makepyfile("def test(): pass") @@ -1237,7 +1700,7 @@ def test_invocation_args(testdir): call = calls[0] config = call.item.config - assert config.invocation_params.args == [p, "-v"] + assert config.invocation_params.args == (p, "-v") assert config.invocation_params.dir == Path(str(testdir.tmpdir)) plugins = config.invocation_params.plugins @@ -1245,6 +1708,10 @@ def test_invocation_args(testdir): assert plugins[0] is plugin assert type(plugins[1]).__name__ == "Collect" # installed by testdir.inline_run() + # args cannot be None + with pytest.raises(TypeError): + Config.InvocationParams(args=None, plugins=None, dir=Path()) # type: ignore[arg-type] + @pytest.mark.parametrize( "plugin", @@ -1269,7 +1736,7 @@ def test_config_blocked_default_plugins(testdir, plugin): result = testdir.runpytest(str(p), "-pno:%s" % plugin) if plugin == "python": - assert result.ret == EXIT_USAGEERROR + assert result.ret == ExitCode.USAGE_ERROR result.stderr.fnmatch_lines( [ "ERROR: not found: */test_config_blocked_default_plugins.py", @@ -1278,14 +1745,205 @@ def test_config_blocked_default_plugins(testdir, plugin): ) return - assert result.ret == EXIT_OK + assert result.ret == ExitCode.OK if plugin != "terminal": result.stdout.fnmatch_lines(["* 1 passed in *"]) p = testdir.makepyfile("def test(): assert 0") result = testdir.runpytest(str(p), "-pno:%s" % plugin) - assert result.ret == EXIT_TESTSFAILED + assert result.ret == ExitCode.TESTS_FAILED if plugin != "terminal": result.stdout.fnmatch_lines(["* 1 failed in *"]) else: - assert result.stdout.lines == [""] + assert result.stdout.lines == [] + + +class TestSetupCfg: + def test_pytest_setup_cfg_unsupported(self, testdir): + testdir.makefile( + ".cfg", + setup=""" + [pytest] + addopts = --verbose + """, + ) + with pytest.raises(pytest.fail.Exception): + testdir.runpytest() + + def test_pytest_custom_cfg_unsupported(self, testdir): + testdir.makefile( + ".cfg", + custom=""" + [pytest] + addopts = --verbose + """, + ) + with pytest.raises(pytest.fail.Exception): + testdir.runpytest("-c", "custom.cfg") + + +class TestPytestPluginsVariable: + def test_pytest_plugins_in_non_top_level_conftest_unsupported(self, testdir): + testdir.makepyfile( + **{ + "subdirectory/conftest.py": """ + pytest_plugins=['capture'] + """ + } + ) + testdir.makepyfile( + """ + def test_func(): + pass + """ + ) + res = testdir.runpytest() + assert res.ret == 2 + msg = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported" + res.stdout.fnmatch_lines( + [ + "*{msg}*".format(msg=msg), + "*subdirectory{sep}conftest.py*".format(sep=os.sep), + ] + ) + + @pytest.mark.parametrize("use_pyargs", [True, False]) + def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs( + self, testdir, use_pyargs + ): + """When using --pyargs, do not emit the warning about non-top-level conftest warnings (#4039, #4044)""" + + files = { + "src/pkg/__init__.py": "", + "src/pkg/conftest.py": "", + "src/pkg/test_root.py": "def test(): pass", + "src/pkg/sub/__init__.py": "", + "src/pkg/sub/conftest.py": "pytest_plugins=['capture']", + "src/pkg/sub/test_bar.py": "def test(): pass", + } + testdir.makepyfile(**files) + testdir.syspathinsert(testdir.tmpdir.join("src")) + + args = ("--pyargs", "pkg") if use_pyargs else () + res = testdir.runpytest(*args) + assert res.ret == (0 if use_pyargs else 2) + msg = ( + msg + ) = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported" + if use_pyargs: + assert msg not in res.stdout.str() + else: + res.stdout.fnmatch_lines(["*{msg}*".format(msg=msg)]) + + def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conftest( + self, testdir + ): + subdirectory = testdir.tmpdir.join("subdirectory") + subdirectory.mkdir() + testdir.makeconftest( + """ + pytest_plugins=['capture'] + """ + ) + testdir.tmpdir.join("conftest.py").move(subdirectory.join("conftest.py")) + + testdir.makepyfile( + """ + def test_func(): + pass + """ + ) + + res = testdir.runpytest_subprocess() + assert res.ret == 2 + msg = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported" + res.stdout.fnmatch_lines( + [ + "*{msg}*".format(msg=msg), + "*subdirectory{sep}conftest.py*".format(sep=os.sep), + ] + ) + + def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives( + self, testdir + ): + testdir.makepyfile( + "def test_func(): pass", + **{ + "subdirectory/conftest": "pass", + "conftest": """ + import warnings + warnings.filterwarnings('always', category=DeprecationWarning) + pytest_plugins=['capture'] + """, + }, + ) + res = testdir.runpytest_subprocess() + assert res.ret == 0 + msg = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported" + assert msg not in res.stdout.str() + + +def test_conftest_import_error_repr(tmpdir): + """`ConftestImportFailure` should use a short error message and readable + path to the failed conftest.py file.""" + path = tmpdir.join("foo/conftest.py") + with pytest.raises( + ConftestImportFailure, + match=re.escape("RuntimeError: some error (from {})".format(path)), + ): + try: + raise RuntimeError("some error") + except Exception as exc: + assert exc.__traceback__ is not None + exc_info = (type(exc), exc, exc.__traceback__) + raise ConftestImportFailure(path, exc_info) from exc + + +def test_strtobool(): + assert _strtobool("YES") + assert not _strtobool("NO") + with pytest.raises(ValueError): + _strtobool("unknown") + + +@pytest.mark.parametrize( + "arg, escape, expected", + [ + ("ignore", False, ("ignore", "", Warning, "", 0)), + ( + "ignore::DeprecationWarning", + False, + ("ignore", "", DeprecationWarning, "", 0), + ), + ( + "ignore:some msg:DeprecationWarning", + False, + ("ignore", "some msg", DeprecationWarning, "", 0), + ), + ( + "ignore::DeprecationWarning:mod", + False, + ("ignore", "", DeprecationWarning, "mod", 0), + ), + ( + "ignore::DeprecationWarning:mod:42", + False, + ("ignore", "", DeprecationWarning, "mod", 42), + ), + ("error:some\\msg:::", True, ("error", "some\\\\msg", Warning, "", 0)), + ("error:::mod\\foo:", True, ("error", "", Warning, "mod\\\\foo\\Z", 0)), + ], +) +def test_parse_warning_filter( + arg: str, escape: bool, expected: "Tuple[str, str, Type[Warning], str, int]" +) -> None: + assert parse_warning_filter(arg, escape=escape) == expected + + +@pytest.mark.parametrize("arg", [":" * 5, "::::-1", "::::not-a-number"]) +def test_parse_warning_filter_failure(arg: str) -> None: + import warnings + + with pytest.raises(warnings._OptionError): + parse_warning_filter(arg, escape=True) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py index f8e3dfa92e4..5a476408013 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_conftest.py @@ -1,17 +1,13 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +import os import textwrap import py import pytest +from _pytest.config import ExitCode from _pytest.config import PytestPluginManager -from _pytest.main import EXIT_NOTESTSCOLLECTED -from _pytest.main import EXIT_OK -from _pytest.main import EXIT_USAGEERROR +from _pytest.pathlib import Path +from _pytest.pathlib import symlink_or_skip def ConftestWithSetinitial(path): @@ -21,18 +17,19 @@ def ConftestWithSetinitial(path): def conftest_setinitial(conftest, args, confcutdir=None): - class Namespace(object): + class Namespace: def __init__(self): self.file_or_dir = args self.confcutdir = str(confcutdir) self.noconftest = False self.pyargs = False + self.importmode = "prepend" conftest._set_initial_conftests(Namespace()) @pytest.mark.usefixtures("_sys_snapshot") -class TestConftestValueAccessGlobal(object): +class TestConftestValueAccessGlobal: @pytest.fixture(scope="module", params=["global", "inpackage"]) def basedir(self, request, tmpdir_factory): tmpdir = tmpdir_factory.mktemp("basedir", numbered=True) @@ -47,35 +44,38 @@ class TestConftestValueAccessGlobal(object): def test_basic_init(self, basedir): conftest = PytestPluginManager() p = basedir.join("adir") - assert conftest._rget_with_confmod("a", p)[1] == 1 + assert conftest._rget_with_confmod("a", p, importmode="prepend")[1] == 1 def test_immediate_initialiation_and_incremental_are_the_same(self, basedir): conftest = PytestPluginManager() assert not len(conftest._dirpath2confmods) - conftest._getconftestmodules(basedir) + conftest._getconftestmodules(basedir, importmode="prepend") snap1 = len(conftest._dirpath2confmods) assert snap1 == 1 - conftest._getconftestmodules(basedir.join("adir")) + conftest._getconftestmodules(basedir.join("adir"), importmode="prepend") assert len(conftest._dirpath2confmods) == snap1 + 1 - conftest._getconftestmodules(basedir.join("b")) + conftest._getconftestmodules(basedir.join("b"), importmode="prepend") assert len(conftest._dirpath2confmods) == snap1 + 2 def test_value_access_not_existing(self, basedir): conftest = ConftestWithSetinitial(basedir) with pytest.raises(KeyError): - conftest._rget_with_confmod("a", basedir) + conftest._rget_with_confmod("a", basedir, importmode="prepend") def test_value_access_by_path(self, basedir): conftest = ConftestWithSetinitial(basedir) adir = basedir.join("adir") - assert conftest._rget_with_confmod("a", adir)[1] == 1 - assert conftest._rget_with_confmod("a", adir.join("b"))[1] == 1.5 + assert conftest._rget_with_confmod("a", adir, importmode="prepend")[1] == 1 + assert ( + conftest._rget_with_confmod("a", adir.join("b"), importmode="prepend")[1] + == 1.5 + ) def test_value_access_with_confmod(self, basedir): startdir = basedir.join("adir", "b") startdir.ensure("xx", dir=True) conftest = ConftestWithSetinitial(startdir) - mod, value = conftest._rget_with_confmod("a", startdir) + mod, value = conftest._rget_with_confmod("a", startdir, importmode="prepend") assert value == 1.5 path = py.path.local(mod.__file__) assert path.dirpath() == basedir.join("adir", "b") @@ -95,7 +95,7 @@ def test_doubledash_considered(testdir): conf.ensure("conftest.py") conftest = PytestPluginManager() conftest_setinitial(conftest, [conf.basename, conf.basename]) - values = conftest._getconftestmodules(conf) + values = conftest._getconftestmodules(conf, importmode="prepend") assert len(values) == 1 @@ -118,13 +118,13 @@ def test_conftest_global_import(testdir): import py, pytest from _pytest.config import PytestPluginManager conf = PytestPluginManager() - mod = conf._importconftest(py.path.local("conftest.py")) + mod = conf._importconftest(py.path.local("conftest.py"), importmode="prepend") assert mod.x == 3 import conftest assert conftest is mod, (conftest, mod) subconf = py.path.local().ensure("sub", "conftest.py") subconf.write("y=4") - mod2 = conf._importconftest(subconf) + mod2 = conf._importconftest(subconf, importmode="prepend") assert mod != mod2 assert mod2.y == 4 import conftest @@ -140,17 +140,17 @@ def test_conftestcutdir(testdir): p = testdir.mkdir("x") conftest = PytestPluginManager() conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p) - values = conftest._getconftestmodules(p) + values = conftest._getconftestmodules(p, importmode="prepend") assert len(values) == 0 - values = conftest._getconftestmodules(conf.dirpath()) + values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") assert len(values) == 0 assert conf not in conftest._conftestpath2mod # but we can still import a conftest directly - conftest._importconftest(conf) - values = conftest._getconftestmodules(conf.dirpath()) + conftest._importconftest(conf, importmode="prepend") + values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") assert values[0].__file__.startswith(str(conf)) # and all sub paths get updated properly - values = conftest._getconftestmodules(p) + values = conftest._getconftestmodules(p, importmode="prepend") assert len(values) == 1 assert values[0].__file__.startswith(str(conf)) @@ -159,7 +159,7 @@ def test_conftestcutdir_inplace_considered(testdir): conf = testdir.makeconftest("") conftest = PytestPluginManager() conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath()) - values = conftest._getconftestmodules(conf.dirpath()) + values = conftest._getconftestmodules(conf.dirpath(), importmode="prepend") assert len(values) == 1 assert values[0].__file__.startswith(str(conf)) @@ -170,11 +170,12 @@ def test_setinitial_conftest_subdirs(testdir, name): subconftest = sub.ensure("conftest.py") conftest = PytestPluginManager() conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir) + key = Path(str(subconftest)).resolve() if name not in ("whatever", ".dotdir"): - assert subconftest in conftest._conftestpath2mod + assert key in conftest._conftestpath2mod assert len(conftest._conftestpath2mod) == 1 else: - assert subconftest not in conftest._conftestpath2mod + assert key not in conftest._conftestpath2mod assert len(conftest._conftestpath2mod) == 0 @@ -191,19 +192,26 @@ def test_conftest_confcutdir(testdir): ) result = testdir.runpytest("-h", "--confcutdir=%s" % x, x) result.stdout.fnmatch_lines(["*--xyz*"]) - assert "warning: could not load initial" not in result.stdout.str() + result.stdout.no_fnmatch_line("*warning: could not load initial*") -@pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="symlink not available on this platform", -) def test_conftest_symlink(testdir): - """Ensure that conftest.py is used for resolved symlinks.""" + """`conftest.py` discovery follows normal path resolution and does not resolve symlinks.""" + # Structure: + # /real + # /real/conftest.py + # /real/app + # /real/app/tests + # /real/app/tests/test_foo.py + + # Links: + # /symlinktests -> /real/app/tests (running at symlinktests should fail) + # /symlink -> /real (running at /symlink should work) + real = testdir.tmpdir.mkdir("real") realtests = real.mkdir("app").mkdir("tests") - testdir.tmpdir.join("symlinktests").mksymlinkto(realtests) - testdir.tmpdir.join("symlink").mksymlinkto(real) + symlink_or_skip(realtests, testdir.tmpdir.join("symlinktests")) + symlink_or_skip(real, testdir.tmpdir.join("symlink")) testdir.makepyfile( **{ "real/app/tests/test_foo.py": "def test1(fixture): pass", @@ -220,38 +228,20 @@ def test_conftest_symlink(testdir): ), } ) + + # Should fail because conftest cannot be found from the link structure. result = testdir.runpytest("-vs", "symlinktests") - result.stdout.fnmatch_lines( - [ - "*conftest_loaded*", - "real/app/tests/test_foo.py::test1 fixture_used", - "PASSED", - ] - ) - assert result.ret == EXIT_OK + result.stdout.fnmatch_lines(["*fixture 'fixture' not found*"]) + assert result.ret == ExitCode.TESTS_FAILED # Should not cause "ValueError: Plugin already registered" (#4174). result = testdir.runpytest("-vs", "symlink") - assert result.ret == EXIT_OK - - realtests.ensure("__init__.py") - result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1") - result.stdout.fnmatch_lines( - [ - "*conftest_loaded*", - "real/app/tests/test_foo.py::test1 fixture_used", - "PASSED", - ] - ) - assert result.ret == EXIT_OK + assert result.ret == ExitCode.OK -@pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="symlink not available on this platform", -) def test_conftest_symlink_files(testdir): - """Check conftest.py loading when running in directory with symlinks.""" + """Symlinked conftest.py are found when pytest is executed in a directory with symlinked + files.""" real = testdir.tmpdir.mkdir("real") source = { "app/test_foo.py": "def test1(fixture): pass", @@ -275,35 +265,45 @@ def test_conftest_symlink_files(testdir): build = testdir.tmpdir.mkdir("build") build.mkdir("app") for f in source: - build.join(f).mksymlinkto(real.join(f)) + symlink_or_skip(real.join(f), build.join(f)) build.chdir() result = testdir.runpytest("-vs", "app/test_foo.py") result.stdout.fnmatch_lines(["*conftest_loaded*", "PASSED"]) - assert result.ret == EXIT_OK + assert result.ret == ExitCode.OK + + +@pytest.mark.skipif( + os.path.normcase("x") != os.path.normcase("X"), + reason="only relevant for case insensitive file systems", +) +def test_conftest_badcase(testdir): + """Check conftest.py loading when directory casing is wrong (#5792).""" + testdir.tmpdir.mkdir("JenkinsRoot").mkdir("test") + source = {"setup.py": "", "test/__init__.py": "", "test/conftest.py": ""} + testdir.makepyfile(**{"JenkinsRoot/%s" % k: v for k, v in source.items()}) + + testdir.tmpdir.join("jenkinsroot/test").chdir() + result = testdir.runpytest() + assert result.ret == ExitCode.NO_TESTS_COLLECTED + + +def test_conftest_uppercase(testdir): + """Check conftest.py whose qualified name contains uppercase characters (#5819)""" + source = {"__init__.py": "", "Foo/conftest.py": "", "Foo/__init__.py": ""} + testdir.makepyfile(**source) + + testdir.tmpdir.chdir() + result = testdir.runpytest() + assert result.ret == ExitCode.NO_TESTS_COLLECTED def test_no_conftest(testdir): testdir.makeconftest("assert 0") result = testdir.runpytest("--noconftest") - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result = testdir.runpytest() - assert result.ret == EXIT_USAGEERROR - - -def test_conftest_existing_resultlog(testdir): - x = testdir.mkdir("tests") - x.join("conftest.py").write( - textwrap.dedent( - """\ - def pytest_addoption(parser): - parser.addoption("--xyz", action="store_true") - """ - ) - ) - testdir.makefile(ext=".log", result="") # Writes result.log - result = testdir.runpytest("-h", "--resultlog", "result.log") - result.stdout.fnmatch_lines(["*--xyz*"]) + assert result.ret == ExitCode.USAGE_ERROR def test_conftest_existing_junitxml(testdir): @@ -327,16 +327,16 @@ def test_conftest_import_order(testdir, monkeypatch): ct2 = sub.join("conftest.py") ct2.write("") - def impct(p): + def impct(p, importmode): return p conftest = PytestPluginManager() conftest._confcutdir = testdir.tmpdir monkeypatch.setattr(conftest, "_importconftest", impct) - assert conftest._getconftestmodules(sub) == [ct1, ct2] + assert conftest._getconftestmodules(sub, importmode="prepend") == [ct1, ct2] -def test_fixture_dependency(testdir, monkeypatch): +def test_fixture_dependency(testdir): ct1 = testdir.makeconftest("") ct1 = testdir.makepyfile("__init__.py") ct1.write("") @@ -401,7 +401,7 @@ def test_conftest_found_with_double_dash(testdir): ) -class TestConftestVisibility(object): +class TestConftestVisibility: def _setup_tree(self, testdir): # for issue616 # example mostly taken from: # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html @@ -460,8 +460,9 @@ class TestConftestVisibility(object): ) ) print("created directory structure:") - for x in testdir.tmpdir.visit(): - print(" " + x.relto(testdir.tmpdir)) + tmppath = Path(str(testdir.tmpdir)) + for x in tmppath.rglob(""): + print(" " + str(x.relative_to(tmppath))) return {"runner": runner, "package": package, "swc": swc, "snc": snc} @@ -627,5 +628,5 @@ def test_required_option_help(testdir): ) ) result = testdir.runpytest("-h", x) - assert "argument --xyz is required" not in result.stdout.str() + result.stdout.no_fnmatch_line("*argument --xyz is required*") assert "general:" in result.stdout.str() diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py new file mode 100644 index 00000000000..948b621f7c7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_debugging.py @@ -0,0 +1,1302 @@ +import os +import sys + +import _pytest._code +import pytest +from _pytest.debugging import _validate_usepdb_cls + +try: + # Type ignored for Python <= 3.6. + breakpoint # type: ignore +except NameError: + SUPPORTS_BREAKPOINT_BUILTIN = False +else: + SUPPORTS_BREAKPOINT_BUILTIN = True + + +_ENVIRON_PYTHONBREAKPOINT = os.environ.get("PYTHONBREAKPOINT", "") + + +@pytest.fixture(autouse=True) +def pdb_env(request): + if "testdir" in request.fixturenames: + # Disable pdb++ with inner tests. + testdir = request.getfixturevalue("testdir") + testdir.monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") + + +def runpdb_and_get_report(testdir, source): + p = testdir.makepyfile(source) + result = testdir.runpytest_inprocess("--pdb", p) + reports = result.reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 3, reports # setup/call/teardown + return reports[1] + + +@pytest.fixture +def custom_pdb_calls(): + called = [] + + # install dummy debugger class and track which methods were called on it + class _CustomPdb: + quitting = False + + def __init__(self, *args, **kwargs): + called.append("init") + + def reset(self): + called.append("reset") + + def interaction(self, *args): + called.append("interaction") + + _pytest._CustomPdb = _CustomPdb # type: ignore + return called + + +@pytest.fixture +def custom_debugger_hook(): + called = [] + + # install dummy debugger class and track which methods were called on it + class _CustomDebugger: + def __init__(self, *args, **kwargs): + called.append("init") + + def reset(self): + called.append("reset") + + def interaction(self, *args): + called.append("interaction") + + def set_trace(self, frame): + print("**CustomDebugger**") + called.append("set_trace") + + _pytest._CustomDebugger = _CustomDebugger # type: ignore + yield called + del _pytest._CustomDebugger # type: ignore + + +class TestPDB: + @pytest.fixture + def pdblist(self, request): + monkeypatch = request.getfixturevalue("monkeypatch") + pdblist = [] + + def mypdb(*args): + pdblist.append(args) + + plugin = request.config.pluginmanager.getplugin("debugging") + monkeypatch.setattr(plugin, "post_mortem", mypdb) + return pdblist + + def test_pdb_on_fail(self, testdir, pdblist): + rep = runpdb_and_get_report( + testdir, + """ + def test_func(): + assert 0 + """, + ) + assert rep.failed + assert len(pdblist) == 1 + tb = _pytest._code.Traceback(pdblist[0][0]) + assert tb[-1].name == "test_func" + + def test_pdb_on_xfail(self, testdir, pdblist): + rep = runpdb_and_get_report( + testdir, + """ + import pytest + @pytest.mark.xfail + def test_func(): + assert 0 + """, + ) + assert "xfail" in rep.keywords + assert not pdblist + + def test_pdb_on_skip(self, testdir, pdblist): + rep = runpdb_and_get_report( + testdir, + """ + import pytest + def test_func(): + pytest.skip("hello") + """, + ) + assert rep.skipped + assert len(pdblist) == 0 + + def test_pdb_on_BdbQuit(self, testdir, pdblist): + rep = runpdb_and_get_report( + testdir, + """ + import bdb + def test_func(): + raise bdb.BdbQuit + """, + ) + assert rep.failed + assert len(pdblist) == 0 + + def test_pdb_on_KeyboardInterrupt(self, testdir, pdblist): + rep = runpdb_and_get_report( + testdir, + """ + def test_func(): + raise KeyboardInterrupt + """, + ) + assert rep.failed + assert len(pdblist) == 1 + + @staticmethod + def flush(child): + if child.isalive(): + # Read if the test has not (e.g. test_pdb_unittest_skip). + child.read() + child.wait() + assert not child.isalive() + + def test_pdb_unittest_postmortem(self, testdir): + p1 = testdir.makepyfile( + """ + import unittest + class Blub(unittest.TestCase): + def tearDown(self): + self.filename = None + def test_false(self): + self.filename = 'debug' + '.me' + assert 0 + """ + ) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("Pdb") + child.sendline("p self.filename") + child.sendeof() + rest = child.read().decode("utf8") + assert "debug.me" in rest + self.flush(child) + + def test_pdb_unittest_skip(self, testdir): + """Test for issue #2137""" + p1 = testdir.makepyfile( + """ + import unittest + @unittest.skipIf(True, 'Skipping also with pdb active') + class MyTestCase(unittest.TestCase): + def test_one(self): + assert 0 + """ + ) + child = testdir.spawn_pytest("-rs --pdb %s" % p1) + child.expect("Skipping also with pdb active") + child.expect_exact("= 1 skipped in") + child.sendeof() + self.flush(child) + + def test_pdb_print_captured_stdout_and_stderr(self, testdir): + p1 = testdir.makepyfile( + """ + def test_1(): + import sys + sys.stderr.write("get\\x20rekt") + print("get\\x20rekt") + assert False + + def test_not_called_due_to_quit(): + pass + """ + ) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("captured stdout") + child.expect("get rekt") + child.expect("captured stderr") + child.expect("get rekt") + child.expect("traceback") + child.expect("def test_1") + child.expect("Pdb") + child.sendeof() + rest = child.read().decode("utf8") + assert "Exit: Quitting debugger" in rest + assert "= 1 failed in" in rest + assert "def test_1" not in rest + assert "get rekt" not in rest + self.flush(child) + + def test_pdb_dont_print_empty_captured_stdout_and_stderr(self, testdir): + p1 = testdir.makepyfile( + """ + def test_1(): + assert False + """ + ) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("Pdb") + output = child.before.decode("utf8") + child.sendeof() + assert "captured stdout" not in output + assert "captured stderr" not in output + self.flush(child) + + @pytest.mark.parametrize("showcapture", ["all", "no", "log"]) + def test_pdb_print_captured_logs(self, testdir, showcapture): + p1 = testdir.makepyfile( + """ + def test_1(): + import logging + logging.warn("get " + "rekt") + assert False + """ + ) + child = testdir.spawn_pytest( + "--show-capture={} --pdb {}".format(showcapture, p1) + ) + if showcapture in ("all", "log"): + child.expect("captured log") + child.expect("get rekt") + child.expect("Pdb") + child.sendeof() + rest = child.read().decode("utf8") + assert "1 failed" in rest + self.flush(child) + + def test_pdb_print_captured_logs_nologging(self, testdir): + p1 = testdir.makepyfile( + """ + def test_1(): + import logging + logging.warn("get " + "rekt") + assert False + """ + ) + child = testdir.spawn_pytest("--show-capture=all --pdb -p no:logging %s" % p1) + child.expect("get rekt") + output = child.before.decode("utf8") + assert "captured log" not in output + child.expect("Pdb") + child.sendeof() + rest = child.read().decode("utf8") + assert "1 failed" in rest + self.flush(child) + + def test_pdb_interaction_exception(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + def globalfunc(): + pass + def test_1(): + pytest.raises(ValueError, globalfunc) + """ + ) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect(".*def test_1") + child.expect(".*pytest.raises.*globalfunc") + child.expect("Pdb") + child.sendline("globalfunc") + child.expect(".*function") + child.sendeof() + child.expect("1 failed") + self.flush(child) + + def test_pdb_interaction_on_collection_issue181(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + xxx + """ + ) + child = testdir.spawn_pytest("--pdb %s" % p1) + # child.expect(".*import pytest.*") + child.expect("Pdb") + child.sendline("c") + child.expect("1 error") + self.flush(child) + + def test_pdb_interaction_on_internal_error(self, testdir): + testdir.makeconftest( + """ + def pytest_runtest_protocol(): + 0/0 + """ + ) + p1 = testdir.makepyfile("def test_func(): pass") + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("Pdb") + + # INTERNALERROR is only displayed once via terminal reporter. + assert ( + len( + [ + x + for x in child.before.decode().splitlines() + if x.startswith("INTERNALERROR> Traceback") + ] + ) + == 1 + ) + + child.sendeof() + self.flush(child) + + def test_pdb_prevent_ConftestImportFailure_hiding_exception(self, testdir): + testdir.makepyfile("def test_func(): pass") + sub_dir = testdir.tmpdir.join("ns").ensure_dir() + sub_dir.join("conftest").new(ext=".py").write("import unknown") + sub_dir.join("test_file").new(ext=".py").write("def test_func(): pass") + + result = testdir.runpytest_subprocess("--pdb", ".") + result.stdout.fnmatch_lines(["-> import unknown"]) + + def test_pdb_interaction_capturing_simple(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + def test_1(): + i = 0 + print("hello17") + pytest.set_trace() + i == 1 + assert 0 + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect(r"test_1\(\)") + child.expect("i == 1") + child.expect("Pdb") + child.sendline("c") + rest = child.read().decode("utf-8") + assert "AssertionError" in rest + assert "1 failed" in rest + assert "def test_1" in rest + assert "hello17" in rest # out is captured + self.flush(child) + + def test_pdb_set_trace_kwargs(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + def test_1(): + i = 0 + print("hello17") + pytest.set_trace(header="== my_header ==") + x = 3 + assert 0 + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("== my_header ==") + assert "PDB set_trace" not in child.before.decode() + child.expect("Pdb") + child.sendline("c") + rest = child.read().decode("utf-8") + assert "1 failed" in rest + assert "def test_1" in rest + assert "hello17" in rest # out is captured + self.flush(child) + + def test_pdb_set_trace_interception(self, testdir): + p1 = testdir.makepyfile( + """ + import pdb + def test_1(): + pdb.set_trace() + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("test_1") + child.expect("Pdb") + child.sendline("q") + rest = child.read().decode("utf8") + assert "no tests ran" in rest + assert "reading from stdin while output" not in rest + assert "BdbQuit" not in rest + self.flush(child) + + def test_pdb_and_capsys(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + def test_1(capsys): + print("hello1") + pytest.set_trace() + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("test_1") + child.send("capsys.readouterr()\n") + child.expect("hello1") + child.sendeof() + child.read() + self.flush(child) + + def test_pdb_with_caplog_on_pdb_invocation(self, testdir): + p1 = testdir.makepyfile( + """ + def test_1(capsys, caplog): + import logging + logging.getLogger(__name__).warning("some_warning") + assert 0 + """ + ) + child = testdir.spawn_pytest("--pdb %s" % str(p1)) + child.send("caplog.record_tuples\n") + child.expect_exact( + "[('test_pdb_with_caplog_on_pdb_invocation', 30, 'some_warning')]" + ) + child.sendeof() + child.read() + self.flush(child) + + def test_set_trace_capturing_afterwards(self, testdir): + p1 = testdir.makepyfile( + """ + import pdb + def test_1(): + pdb.set_trace() + def test_2(): + print("hello") + assert 0 + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("test_1") + child.send("c\n") + child.expect("test_2") + child.expect("Captured") + child.expect("hello") + child.sendeof() + child.read() + self.flush(child) + + def test_pdb_interaction_doctest(self, testdir): + p1 = testdir.makepyfile( + """ + def function_1(): + ''' + >>> i = 0 + >>> assert i == 1 + ''' + """ + ) + child = testdir.spawn_pytest("--doctest-modules --pdb %s" % p1) + child.expect("Pdb") + + assert "UNEXPECTED EXCEPTION: AssertionError()" in child.before.decode("utf8") + + child.sendline("'i=%i.' % i") + child.expect("Pdb") + assert "\r\n'i=0.'\r\n" in child.before.decode("utf8") + + child.sendeof() + rest = child.read().decode("utf8") + assert "! _pytest.outcomes.Exit: Quitting debugger !" in rest + assert "BdbQuit" not in rest + assert "1 failed" in rest + self.flush(child) + + def test_doctest_set_trace_quit(self, testdir): + p1 = testdir.makepyfile( + """ + def function_1(): + ''' + >>> __import__('pdb').set_trace() + ''' + """ + ) + # NOTE: does not use pytest.set_trace, but Python's patched pdb, + # therefore "-s" is required. + child = testdir.spawn_pytest("--doctest-modules --pdb -s %s" % p1) + child.expect("Pdb") + child.sendline("q") + rest = child.read().decode("utf8") + + assert "! _pytest.outcomes.Exit: Quitting debugger !" in rest + assert "= no tests ran in" in rest + assert "BdbQuit" not in rest + assert "UNEXPECTED EXCEPTION" not in rest + + def test_pdb_interaction_capturing_twice(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + def test_1(): + i = 0 + print("hello17") + pytest.set_trace() + x = 3 + print("hello18") + pytest.set_trace() + x = 4 + assert 0 + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect(r"PDB set_trace \(IO-capturing turned off\)") + child.expect("test_1") + child.expect("x = 3") + child.expect("Pdb") + child.sendline("c") + child.expect(r"PDB continue \(IO-capturing resumed\)") + child.expect(r"PDB set_trace \(IO-capturing turned off\)") + child.expect("x = 4") + child.expect("Pdb") + child.sendline("c") + child.expect("_ test_1 _") + child.expect("def test_1") + rest = child.read().decode("utf8") + assert "Captured stdout call" in rest + assert "hello17" in rest # out is captured + assert "hello18" in rest # out is captured + assert "1 failed" in rest + self.flush(child) + + def test_pdb_with_injected_do_debug(self, testdir): + """Simulates pdbpp, which injects Pdb into do_debug, and uses + self.__class__ in do_continue. + """ + p1 = testdir.makepyfile( + mytest=""" + import pdb + import pytest + + count_continue = 0 + + class CustomPdb(pdb.Pdb, object): + def do_debug(self, arg): + import sys + import types + + do_debug_func = pdb.Pdb.do_debug + + newglobals = do_debug_func.__globals__.copy() + newglobals['Pdb'] = self.__class__ + orig_do_debug = types.FunctionType( + do_debug_func.__code__, newglobals, + do_debug_func.__name__, do_debug_func.__defaults__, + ) + return orig_do_debug(self, arg) + do_debug.__doc__ = pdb.Pdb.do_debug.__doc__ + + def do_continue(self, *args, **kwargs): + global count_continue + count_continue += 1 + return super(CustomPdb, self).do_continue(*args, **kwargs) + + def foo(): + print("print_from_foo") + + def test_1(): + i = 0 + print("hello17") + pytest.set_trace() + x = 3 + print("hello18") + + assert count_continue == 2, "unexpected_failure: %d != 2" % count_continue + pytest.fail("expected_failure") + """ + ) + child = testdir.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1)) + child.expect(r"PDB set_trace \(IO-capturing turned off\)") + child.expect(r"\n\(Pdb") + child.sendline("debug foo()") + child.expect("ENTERING RECURSIVE DEBUGGER") + child.expect(r"\n\(\(Pdb") + child.sendline("c") + child.expect("LEAVING RECURSIVE DEBUGGER") + assert b"PDB continue" not in child.before + # No extra newline. + assert child.before.endswith(b"c\r\nprint_from_foo\r\n") + + # set_debug should not raise outcomes. Exit, if used recursively. + child.sendline("debug 42") + child.sendline("q") + child.expect("LEAVING RECURSIVE DEBUGGER") + assert b"ENTERING RECURSIVE DEBUGGER" in child.before + assert b"Quitting debugger" not in child.before + + child.sendline("c") + child.expect(r"PDB continue \(IO-capturing resumed\)") + rest = child.read().decode("utf8") + assert "hello17" in rest # out is captured + assert "hello18" in rest # out is captured + assert "1 failed" in rest + assert "Failed: expected_failure" in rest + assert "AssertionError: unexpected_failure" not in rest + self.flush(child) + + def test_pdb_without_capture(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + def test_1(): + pytest.set_trace() + """ + ) + child = testdir.spawn_pytest("-s %s" % p1) + child.expect(r">>> PDB set_trace >>>") + child.expect("Pdb") + child.sendline("c") + child.expect(r">>> PDB continue >>>") + child.expect("1 passed") + self.flush(child) + + @pytest.mark.parametrize("capture_arg", ("", "-s", "-p no:capture")) + def test_pdb_continue_with_recursive_debug(self, capture_arg, testdir): + """Full coverage for do_debug without capturing. + + This is very similar to test_pdb_interaction_continue_recursive in general, + but mocks out ``pdb.set_trace`` for providing more coverage. + """ + p1 = testdir.makepyfile( + """ + try: + input = raw_input + except NameError: + pass + + def set_trace(): + __import__('pdb').set_trace() + + def test_1(monkeypatch): + import _pytest.debugging + + class pytestPDBTest(_pytest.debugging.pytestPDB): + @classmethod + def set_trace(cls, *args, **kwargs): + # Init PytestPdbWrapper to handle capturing. + _pdb = cls._init_pdb("set_trace", *args, **kwargs) + + # Mock out pdb.Pdb.do_continue. + import pdb + pdb.Pdb.do_continue = lambda self, arg: None + + print("===" + " SET_TRACE ===") + assert input() == "debug set_trace()" + + # Simulate PytestPdbWrapper.do_debug + cls._recursive_debug += 1 + print("ENTERING RECURSIVE DEBUGGER") + print("===" + " SET_TRACE_2 ===") + + assert input() == "c" + _pdb.do_continue("") + print("===" + " SET_TRACE_3 ===") + + # Simulate PytestPdbWrapper.do_debug + print("LEAVING RECURSIVE DEBUGGER") + cls._recursive_debug -= 1 + + print("===" + " SET_TRACE_4 ===") + assert input() == "c" + _pdb.do_continue("") + + def do_continue(self, arg): + print("=== do_continue") + + monkeypatch.setattr(_pytest.debugging, "pytestPDB", pytestPDBTest) + + import pdb + monkeypatch.setattr(pdb, "set_trace", pytestPDBTest.set_trace) + + set_trace() + """ + ) + child = testdir.spawn_pytest("--tb=short {} {}".format(p1, capture_arg)) + child.expect("=== SET_TRACE ===") + before = child.before.decode("utf8") + if not capture_arg: + assert ">>> PDB set_trace (IO-capturing turned off) >>>" in before + else: + assert ">>> PDB set_trace >>>" in before + child.sendline("debug set_trace()") + child.expect("=== SET_TRACE_2 ===") + before = child.before.decode("utf8") + assert "\r\nENTERING RECURSIVE DEBUGGER\r\n" in before + child.sendline("c") + child.expect("=== SET_TRACE_3 ===") + + # No continue message with recursive debugging. + before = child.before.decode("utf8") + assert ">>> PDB continue " not in before + + child.sendline("c") + child.expect("=== SET_TRACE_4 ===") + before = child.before.decode("utf8") + assert "\r\nLEAVING RECURSIVE DEBUGGER\r\n" in before + child.sendline("c") + rest = child.read().decode("utf8") + if not capture_arg: + assert "> PDB continue (IO-capturing resumed) >" in rest + else: + assert "> PDB continue >" in rest + assert "= 1 passed in" in rest + + def test_pdb_used_outside_test(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + pytest.set_trace() + x = 5 + """ + ) + child = testdir.spawn("{} {}".format(sys.executable, p1)) + child.expect("x = 5") + child.expect("Pdb") + child.sendeof() + self.flush(child) + + def test_pdb_used_in_generate_tests(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + def pytest_generate_tests(metafunc): + pytest.set_trace() + x = 5 + def test_foo(a): + pass + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("x = 5") + child.expect("Pdb") + child.sendeof() + self.flush(child) + + def test_pdb_collection_failure_is_shown(self, testdir): + p1 = testdir.makepyfile("xxx") + result = testdir.runpytest_subprocess("--pdb", p1) + result.stdout.fnmatch_lines( + ["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF + ) + + @pytest.mark.parametrize("post_mortem", (False, True)) + def test_enter_leave_pdb_hooks_are_called(self, post_mortem, testdir): + testdir.makeconftest( + """ + mypdb = None + + def pytest_configure(config): + config.testing_verification = 'configured' + + def pytest_enter_pdb(config, pdb): + assert config.testing_verification == 'configured' + print('enter_pdb_hook') + + global mypdb + mypdb = pdb + mypdb.set_attribute = "bar" + + def pytest_leave_pdb(config, pdb): + assert config.testing_verification == 'configured' + print('leave_pdb_hook') + + global mypdb + assert mypdb is pdb + assert mypdb.set_attribute == "bar" + """ + ) + p1 = testdir.makepyfile( + """ + import pytest + + def test_set_trace(): + pytest.set_trace() + assert 0 + + def test_post_mortem(): + assert 0 + """ + ) + if post_mortem: + child = testdir.spawn_pytest(str(p1) + " --pdb -s -k test_post_mortem") + else: + child = testdir.spawn_pytest(str(p1) + " -k test_set_trace") + child.expect("enter_pdb_hook") + child.sendline("c") + if post_mortem: + child.expect(r"PDB continue") + else: + child.expect(r"PDB continue \(IO-capturing resumed\)") + child.expect("Captured stdout call") + rest = child.read().decode("utf8") + assert "leave_pdb_hook" in rest + assert "1 failed" in rest + self.flush(child) + + def test_pdb_custom_cls(self, testdir, custom_pdb_calls): + p1 = testdir.makepyfile("""xxx """) + result = testdir.runpytest_inprocess("--pdb", "--pdbcls=_pytest:_CustomPdb", p1) + result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"]) + assert custom_pdb_calls == ["init", "reset", "interaction"] + + def test_pdb_custom_cls_invalid(self, testdir): + result = testdir.runpytest_inprocess("--pdbcls=invalid") + result.stderr.fnmatch_lines( + [ + "*: error: argument --pdbcls: 'invalid' is not in the format 'modname:classname'" + ] + ) + + def test_pdb_validate_usepdb_cls(self): + assert _validate_usepdb_cls("os.path:dirname.__name__") == ( + "os.path", + "dirname.__name__", + ) + + assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist") + + def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls): + p1 = testdir.makepyfile("""xxx """) + result = testdir.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1) + result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"]) + assert custom_pdb_calls == [] + + def test_pdb_custom_cls_with_set_trace(self, testdir, monkeypatch): + testdir.makepyfile( + custom_pdb=""" + class CustomPdb(object): + def __init__(self, *args, **kwargs): + skip = kwargs.pop("skip") + assert skip == ["foo.*"] + print("__init__") + super(CustomPdb, self).__init__(*args, **kwargs) + + def set_trace(*args, **kwargs): + print('custom set_trace>') + """ + ) + p1 = testdir.makepyfile( + """ + import pytest + + def test_foo(): + pytest.set_trace(skip=['foo.*']) + """ + ) + monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir)) + child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1)) + + child.expect("__init__") + child.expect("custom set_trace>") + self.flush(child) + + +class TestDebuggingBreakpoints: + def test_supports_breakpoint_module_global(self): + """ + Test that supports breakpoint global marks on Python 3.7+ and not on + CPython 3.5, 2.7 + """ + if sys.version_info >= (3, 7): + assert SUPPORTS_BREAKPOINT_BUILTIN is True + if sys.version_info.major == 3 and sys.version_info.minor == 5: + assert SUPPORTS_BREAKPOINT_BUILTIN is False # type: ignore[comparison-overlap] + + @pytest.mark.skipif( + not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" + ) + @pytest.mark.parametrize("arg", ["--pdb", ""]) + def test_sys_breakpointhook_configure_and_unconfigure(self, testdir, arg): + """ + Test that sys.breakpointhook is set to the custom Pdb class once configured, test that + hook is reset to system value once pytest has been unconfigured + """ + testdir.makeconftest( + """ + import sys + from pytest import hookimpl + from _pytest.debugging import pytestPDB + + def pytest_configure(config): + config._cleanup.append(check_restored) + + def check_restored(): + assert sys.breakpointhook == sys.__breakpointhook__ + + def test_check(): + assert sys.breakpointhook == pytestPDB.set_trace + """ + ) + testdir.makepyfile( + """ + def test_nothing(): pass + """ + ) + args = (arg,) if arg else () + result = testdir.runpytest_subprocess(*args) + result.stdout.fnmatch_lines(["*1 passed in *"]) + + @pytest.mark.skipif( + not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" + ) + def test_pdb_custom_cls(self, testdir, custom_debugger_hook): + p1 = testdir.makepyfile( + """ + def test_nothing(): + breakpoint() + """ + ) + result = testdir.runpytest_inprocess( + "--pdb", "--pdbcls=_pytest:_CustomDebugger", p1 + ) + result.stdout.fnmatch_lines(["*CustomDebugger*", "*1 passed*"]) + assert custom_debugger_hook == ["init", "set_trace"] + + @pytest.mark.parametrize("arg", ["--pdb", ""]) + @pytest.mark.skipif( + not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" + ) + def test_environ_custom_class(self, testdir, custom_debugger_hook, arg): + testdir.makeconftest( + """ + import os + import sys + + os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace' + + def pytest_configure(config): + config._cleanup.append(check_restored) + + def check_restored(): + assert sys.breakpointhook == sys.__breakpointhook__ + + def test_check(): + import _pytest + assert sys.breakpointhook is _pytest._CustomDebugger.set_trace + """ + ) + testdir.makepyfile( + """ + def test_nothing(): pass + """ + ) + args = (arg,) if arg else () + result = testdir.runpytest_subprocess(*args) + result.stdout.fnmatch_lines(["*1 passed in *"]) + + @pytest.mark.skipif( + not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" + ) + @pytest.mark.skipif( + not _ENVIRON_PYTHONBREAKPOINT == "", + reason="Requires breakpoint() default value", + ) + def test_sys_breakpoint_interception(self, testdir): + p1 = testdir.makepyfile( + """ + def test_1(): + breakpoint() + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("test_1") + child.expect("Pdb") + child.sendline("quit") + rest = child.read().decode("utf8") + assert "Quitting debugger" in rest + assert "reading from stdin while output" not in rest + TestPDB.flush(child) + + @pytest.mark.skipif( + not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" + ) + def test_pdb_not_altered(self, testdir): + p1 = testdir.makepyfile( + """ + import pdb + def test_1(): + pdb.set_trace() + assert 0 + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("test_1") + child.expect("Pdb") + child.sendline("c") + rest = child.read().decode("utf8") + assert "1 failed" in rest + assert "reading from stdin while output" not in rest + TestPDB.flush(child) + + +class TestTraceOption: + def test_trace_sets_breakpoint(self, testdir): + p1 = testdir.makepyfile( + """ + def test_1(): + assert True + + def test_2(): + pass + + def test_3(): + pass + """ + ) + child = testdir.spawn_pytest("--trace " + str(p1)) + child.expect("test_1") + child.expect("Pdb") + child.sendline("c") + child.expect("test_2") + child.expect("Pdb") + child.sendline("c") + child.expect("test_3") + child.expect("Pdb") + child.sendline("q") + child.expect_exact("Exit: Quitting debugger") + rest = child.read().decode("utf8") + assert "= 2 passed in" in rest + assert "reading from stdin while output" not in rest + # Only printed once - not on stderr. + assert "Exit: Quitting debugger" not in child.before.decode("utf8") + TestPDB.flush(child) + + def test_trace_with_parametrize_handles_shared_fixtureinfo(self, testdir): + p1 = testdir.makepyfile( + """ + import pytest + @pytest.mark.parametrize('myparam', [1,2]) + def test_1(myparam, request): + assert myparam in (1, 2) + assert request.function.__name__ == "test_1" + @pytest.mark.parametrize('func', [1,2]) + def test_func(func, request): + assert func in (1, 2) + assert request.function.__name__ == "test_func" + @pytest.mark.parametrize('myparam', [1,2]) + def test_func_kw(myparam, request, func="func_kw"): + assert myparam in (1, 2) + assert func == "func_kw" + assert request.function.__name__ == "test_func_kw" + """ + ) + child = testdir.spawn_pytest("--trace " + str(p1)) + for func, argname in [ + ("test_1", "myparam"), + ("test_func", "func"), + ("test_func_kw", "myparam"), + ]: + child.expect_exact("> PDB runcall (IO-capturing turned off) >") + child.expect_exact(func) + child.expect_exact("Pdb") + child.sendline("args") + child.expect_exact("{} = 1\r\n".format(argname)) + child.expect_exact("Pdb") + child.sendline("c") + child.expect_exact("Pdb") + child.sendline("args") + child.expect_exact("{} = 2\r\n".format(argname)) + child.expect_exact("Pdb") + child.sendline("c") + child.expect_exact("> PDB continue (IO-capturing resumed) >") + rest = child.read().decode("utf8") + assert "= 6 passed in" in rest + assert "reading from stdin while output" not in rest + # Only printed once - not on stderr. + assert "Exit: Quitting debugger" not in child.before.decode("utf8") + TestPDB.flush(child) + + +def test_trace_after_runpytest(testdir): + """Test that debugging's pytest_configure is re-entrant.""" + p1 = testdir.makepyfile( + """ + from _pytest.debugging import pytestPDB + + def test_outer(testdir): + assert len(pytestPDB._saved) == 1 + + testdir.makepyfile( + \""" + from _pytest.debugging import pytestPDB + + def test_inner(): + assert len(pytestPDB._saved) == 2 + print() + print("test_inner_" + "end") + \""" + ) + + result = testdir.runpytest("-s", "-k", "test_inner") + assert result.ret == 0 + + assert len(pytestPDB._saved) == 1 + """ + ) + result = testdir.runpytest_subprocess("-s", "-p", "pytester", str(p1)) + result.stdout.fnmatch_lines(["test_inner_end"]) + assert result.ret == 0 + + +def test_quit_with_swallowed_SystemExit(testdir): + """Test that debugging's pytest_configure is re-entrant.""" + p1 = testdir.makepyfile( + """ + def call_pdb_set_trace(): + __import__('pdb').set_trace() + + + def test_1(): + try: + call_pdb_set_trace() + except SystemExit: + pass + + + def test_2(): + pass + """ + ) + child = testdir.spawn_pytest(str(p1)) + child.expect("Pdb") + child.sendline("q") + child.expect_exact("Exit: Quitting debugger") + rest = child.read().decode("utf8") + assert "no tests ran" in rest + TestPDB.flush(child) + + +@pytest.mark.parametrize("fixture", ("capfd", "capsys")) +def test_pdb_suspends_fixture_capturing(testdir, fixture): + """Using "-s" with pytest should suspend/resume fixture capturing.""" + p1 = testdir.makepyfile( + """ + def test_inner({fixture}): + import sys + + print("out_inner_before") + sys.stderr.write("err_inner_before\\n") + + __import__("pdb").set_trace() + + print("out_inner_after") + sys.stderr.write("err_inner_after\\n") + + out, err = {fixture}.readouterr() + assert out =="out_inner_before\\nout_inner_after\\n" + assert err =="err_inner_before\\nerr_inner_after\\n" + """.format( + fixture=fixture + ) + ) + + child = testdir.spawn_pytest(str(p1) + " -s") + + child.expect("Pdb") + before = child.before.decode("utf8") + assert ( + "> PDB set_trace (IO-capturing turned off for fixture %s) >" % (fixture) + in before + ) + + # Test that capturing is really suspended. + child.sendline("p 40 + 2") + child.expect("Pdb") + assert "\r\n42\r\n" in child.before.decode("utf8") + + child.sendline("c") + rest = child.read().decode("utf8") + assert "out_inner" not in rest + assert "err_inner" not in rest + + TestPDB.flush(child) + assert child.exitstatus == 0 + assert "= 1 passed in" in rest + assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest + + +def test_pdbcls_via_local_module(testdir): + """It should be imported in pytest_configure or later only.""" + p1 = testdir.makepyfile( + """ + def test(): + print("before_set_trace") + __import__("pdb").set_trace() + """, + mypdb=""" + class Wrapped: + class MyPdb: + def set_trace(self, *args): + print("set_trace_called", args) + + def runcall(self, *args, **kwds): + print("runcall_called", args, kwds) + """, + ) + result = testdir.runpytest( + str(p1), "--pdbcls=really.invalid:Value", syspathinsert=True + ) + result.stdout.fnmatch_lines( + [ + "*= FAILURES =*", + "E * --pdbcls: could not import 'really.invalid:Value': No module named *really*", + ] + ) + assert result.ret == 1 + + result = testdir.runpytest( + str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True + ) + assert result.ret == 0 + result.stdout.fnmatch_lines(["*set_trace_called*", "* 1 passed in *"]) + + # Ensure that it also works with --trace. + result = testdir.runpytest( + str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", "--trace", syspathinsert=True + ) + assert result.ret == 0 + result.stdout.fnmatch_lines(["*runcall_called*", "* 1 passed in *"]) + + +def test_raises_bdbquit_with_eoferror(testdir): + """It is not guaranteed that DontReadFromInput's read is called.""" + + p1 = testdir.makepyfile( + """ + def input_without_read(*args, **kwargs): + raise EOFError() + + def test(monkeypatch): + import builtins + monkeypatch.setattr(builtins, "input", input_without_read) + __import__('pdb').set_trace() + """ + ) + result = testdir.runpytest(str(p1)) + result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"]) + assert result.ret == 1 + + +def test_pdb_wrapper_class_is_reused(testdir): + p1 = testdir.makepyfile( + """ + def test(): + __import__("pdb").set_trace() + __import__("pdb").set_trace() + + import mypdb + instances = mypdb.instances + assert len(instances) == 2 + assert instances[0].__class__ is instances[1].__class__ + """, + mypdb=""" + instances = [] + + class MyPdb: + def __init__(self, *args, **kwargs): + instances.append(self) + + def set_trace(self, *args): + print("set_trace_called", args) + """, + ) + result = testdir.runpytest(str(p1), "--pdbcls=mypdb:MyPdb", syspathinsert=True) + assert result.ret == 0 + result.stdout.fnmatch_lines( + ["*set_trace_called*", "*set_trace_called*", "* 1 passed in *"] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py index 723f1fe9642..0b32ad32203 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_doctest.py @@ -1,22 +1,20 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import inspect -import sys import textwrap +from typing import Callable +from typing import Optional import pytest from _pytest.compat import MODULE_NOT_FOUND_ERROR +from _pytest.doctest import _get_checker from _pytest.doctest import _is_mocked +from _pytest.doctest import _is_setup_py from _pytest.doctest import _patch_unwrap_mock_aware from _pytest.doctest import DoctestItem from _pytest.doctest import DoctestModule from _pytest.doctest import DoctestTextfile -class TestDoctests(object): +class TestDoctests: def test_collect_testtextfile(self, testdir): w = testdir.maketxtfile(whatever="") checkfile = testdir.maketxtfile( @@ -117,8 +115,7 @@ class TestDoctests(object): reprec.assertoutcome(failed=1) def test_multiple_patterns(self, testdir): - """Test support for multiple --doctest-glob arguments (#1255). - """ + """Test support for multiple --doctest-glob arguments (#1255).""" testdir.maketxtfile( xdoc=""" >>> 1 @@ -148,11 +145,10 @@ class TestDoctests(object): @pytest.mark.parametrize( " test_string, encoding", - [(u"foo", "ascii"), (u"öäü", "latin1"), (u"öäü", "utf-8")], + [("foo", "ascii"), ("öäü", "latin1"), ("öäü", "utf-8")], ) def test_encoding(self, testdir, test_string, encoding): - """Test support for doctest_encoding ini option. - """ + """Test support for doctest_encoding ini option.""" testdir.makeini( """ [pytest] @@ -161,8 +157,8 @@ class TestDoctests(object): encoding ) ) - doctest = u""" - >>> u"{}" + doctest = """ + >>> "{}" {} """.format( test_string, repr(test_string) @@ -184,24 +180,58 @@ class TestDoctests(object): result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines( [ - "*unexpected_exception*", - "*>>> i = 0*", - "*>>> 0 / i*", - "*UNEXPECTED*ZeroDivision*", - ] - ) - - def test_doctest_skip(self, testdir): + "test_doctest_unexpected_exception.txt F *", + "", + "*= FAILURES =*", + "*_ [[]doctest[]] test_doctest_unexpected_exception.txt _*", + "001 >>> i = 0", + "002 >>> 0 / i", + "UNEXPECTED EXCEPTION: ZeroDivisionError*", + "Traceback (most recent call last):", + ' File "*/doctest.py", line *, in __run', + " *", + ' File "<doctest test_doctest_unexpected_exception.txt[1]>", line 1, in <module>', + "ZeroDivisionError: division by zero", + "*/test_doctest_unexpected_exception.txt:2: UnexpectedException", + ], + consecutive=True, + ) + + def test_doctest_outcomes(self, testdir): testdir.maketxtfile( - """ + test_skip=""" >>> 1 1 >>> import pytest >>> pytest.skip("") - """ + >>> 2 + 3 + """, + test_xfail=""" + >>> import pytest + >>> pytest.xfail("xfail_reason") + >>> foo + bar + """, + test_importorskip=""" + >>> import pytest + >>> pytest.importorskip("doesnotexist") + >>> foo + bar + """, ) result = testdir.runpytest("--doctest-modules") - result.stdout.fnmatch_lines(["*1 skipped*"]) + result.stdout.fnmatch_lines( + [ + "collected 3 items", + "", + "test_importorskip.txt s *", + "test_skip.txt s *", + "test_xfail.txt x *", + "", + "*= 2 skipped, 1 xfailed in *", + ] + ) def test_docstring_partial_context_around_error(self, testdir): """Test that we show some context before the actual line of a failing @@ -244,8 +274,8 @@ class TestDoctests(object): ] ) # lines below should be trimmed out - assert "text-line-2" not in result.stdout.str() - assert "text-line-after" not in result.stdout.str() + result.stdout.no_fnmatch_line("*text-line-2*") + result.stdout.no_fnmatch_line("*text-line-after*") def test_docstring_full_context_around_error(self, testdir): """Test that we show the whole context before the actual line of a failing @@ -293,12 +323,67 @@ class TestDoctests(object): ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines( + ["*hello*", "006*>>> 1/0*", "*UNEXPECTED*ZeroDivision*", "*1 failed*"] + ) + + def test_doctest_linedata_on_property(self, testdir): + testdir.makepyfile( + """ + class Sample(object): + @property + def some_property(self): + ''' + >>> Sample().some_property + 'another thing' + ''' + return 'something' + """ + ) + result = testdir.runpytest("--doctest-modules") + result.stdout.fnmatch_lines( [ - "*hello*", - "*EXAMPLE LOCATION UNKNOWN, not showing all tests of that example*", - "*1/0*", - "*UNEXPECTED*ZeroDivision*", - "*1 failed*", + "*= FAILURES =*", + "*_ [[]doctest[]] test_doctest_linedata_on_property.Sample.some_property _*", + "004 ", + "005 >>> Sample().some_property", + "Expected:", + " 'another thing'", + "Got:", + " 'something'", + "", + "*/test_doctest_linedata_on_property.py:5: DocTestFailure", + "*= 1 failed in *", + ] + ) + + def test_doctest_no_linedata_on_overriden_property(self, testdir): + testdir.makepyfile( + """ + class Sample(object): + @property + def some_property(self): + ''' + >>> Sample().some_property + 'another thing' + ''' + return 'something' + some_property = property(some_property.__get__, None, None, some_property.__doc__) + """ + ) + result = testdir.runpytest("--doctest-modules") + result.stdout.fnmatch_lines( + [ + "*= FAILURES =*", + "*_ [[]doctest[]] test_doctest_no_linedata_on_overriden_property.Sample.some_property _*", + "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example", + "[?][?][?] >>> Sample().some_property", + "Expected:", + " 'another thing'", + "Got:", + " 'something'", + "", + "*/test_doctest_no_linedata_on_overriden_property.py:None: DocTestFailure", + "*= 1 failed in *", ] ) @@ -339,7 +424,7 @@ class TestDoctests(object): [ "*ERROR collecting hello.py*", "*{e}: No module named *asdals*".format(e=MODULE_NOT_FOUND_ERROR), - "*Interrupted: 1 errors during collection*", + "*Interrupted: 1 error during collection*", ] ) @@ -580,17 +665,15 @@ class TestDoctests(object): reprec.assertoutcome(failed=1, passed=0) def test_contains_unicode(self, testdir): - """Fix internal error with docstrings containing non-ascii characters. - """ + """Fix internal error with docstrings containing non-ascii characters.""" testdir.makepyfile( - u''' - # -*- coding: utf-8 -*- + '''\ def foo(): """ >>> name = 'с' # not letter 'c' but instead Cyrillic 's'. 'anything' """ - ''' + ''' ) result = testdir.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["Got nothing", "* 1 failed in*"]) @@ -615,9 +698,7 @@ class TestDoctests(object): reprec.assertoutcome(skipped=1, failed=1, passed=0) def test_junit_report_for_doctest(self, testdir): - """ - #713: Fix --junit-xml option when used with --doctest-modules. - """ + """#713: Fix --junit-xml option when used with --doctest-modules.""" p = testdir.makepyfile( """ def foo(): @@ -661,9 +742,6 @@ class TestDoctests(object): """ p = testdir.makepyfile( test_unicode_doctest_module=""" - # -*- coding: utf-8 -*- - from __future__ import unicode_literals - def fix_bad_unicode(text): ''' >>> print(fix_bad_unicode('único')) @@ -684,7 +762,7 @@ class TestDoctests(object): test_print_unicode_value=r""" Here is a doctest:: - >>> print(u'\xE5\xE9\xEE\xF8\xFC') + >>> print('\xE5\xE9\xEE\xF8\xFC') åéîøü """ ) @@ -692,9 +770,7 @@ class TestDoctests(object): result.stdout.fnmatch_lines(["* 1 passed *"]) def test_reportinfo(self, testdir): - """ - Test case to make sure that DoctestItem.reportinfo() returns lineno. - """ + """Make sure that DoctestItem.reportinfo() returns lineno.""" p = testdir.makepyfile( test_reportinfo=""" def foo(x): @@ -742,7 +818,7 @@ class TestDoctests(object): result.stdout.fnmatch_lines(["*collected 1 item*"]) -class TestLiterals(object): +class TestLiterals: @pytest.mark.parametrize("config_mode", ["ini", "comment"]) def test_allow_unicode(self, testdir, config_mode): """Test that doctests which output unicode work in all python versions @@ -833,13 +909,11 @@ class TestLiterals(object): """ ) reprec = testdir.inline_run() - passed = int(sys.version_info[0] >= 3) - reprec.assertoutcome(passed=passed, failed=int(not passed)) + reprec.assertoutcome(passed=1) def test_bytes_literal(self, testdir): """Test that doctests which output bytes fail in Python 3 when - the ALLOW_BYTES option is not used. The same test should pass - in Python 2 (#1287). + the ALLOW_BYTES option is not used. (#1287). """ testdir.maketxtfile( test_doc=""" @@ -848,11 +922,159 @@ class TestLiterals(object): """ ) reprec = testdir.inline_run() - passed = int(sys.version_info[0] == 2) - reprec.assertoutcome(passed=passed, failed=int(not passed)) + reprec.assertoutcome(failed=1) + + def test_number_re(self) -> None: + _number_re = _get_checker()._number_re # type: ignore + for s in [ + "1.", + "+1.", + "-1.", + ".1", + "+.1", + "-.1", + "0.1", + "+0.1", + "-0.1", + "1e5", + "+1e5", + "1e+5", + "+1e+5", + "1e-5", + "+1e-5", + "-1e-5", + "1.2e3", + "-1.2e-3", + ]: + print(s) + m = _number_re.match(s) + assert m is not None + assert float(m.group()) == pytest.approx(float(s)) + for s in ["1", "abc"]: + print(s) + assert _number_re.match(s) is None + + @pytest.mark.parametrize("config_mode", ["ini", "comment"]) + def test_number_precision(self, testdir, config_mode): + """Test the NUMBER option.""" + if config_mode == "ini": + testdir.makeini( + """ + [pytest] + doctest_optionflags = NUMBER + """ + ) + comment = "" + else: + comment = "#doctest: +NUMBER" + + testdir.maketxtfile( + test_doc=""" + + Scalars: + + >>> import math + >>> math.pi {comment} + 3.141592653589793 + >>> math.pi {comment} + 3.1416 + >>> math.pi {comment} + 3.14 + >>> -math.pi {comment} + -3.14 + >>> math.pi {comment} + 3. + >>> 3. {comment} + 3.0 + >>> 3. {comment} + 3. + >>> 3. {comment} + 3.01 + >>> 3. {comment} + 2.99 + >>> .299 {comment} + .3 + >>> .301 {comment} + .3 + >>> 951. {comment} + 1e3 + >>> 1049. {comment} + 1e3 + >>> -1049. {comment} + -1e3 + >>> 1e3 {comment} + 1e3 + >>> 1e3 {comment} + 1000. + + Lists: + + >>> [3.1415, 0.097, 13.1, 7, 8.22222e5, 0.598e-2] {comment} + [3.14, 0.1, 13., 7, 8.22e5, 6.0e-3] + >>> [[0.333, 0.667], [0.999, 1.333]] {comment} + [[0.33, 0.667], [0.999, 1.333]] + >>> [[[0.101]]] {comment} + [[[0.1]]] + + Doesn't barf on non-numbers: + + >>> 'abc' {comment} + 'abc' + >>> None {comment} + """.format( + comment=comment + ) + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) + + @pytest.mark.parametrize( + "expression,output", + [ + # ints shouldn't match floats: + ("3.0", "3"), + ("3e0", "3"), + ("1e3", "1000"), + ("3", "3.0"), + # Rounding: + ("3.1", "3.0"), + ("3.1", "3.2"), + ("3.1", "4.0"), + ("8.22e5", "810000.0"), + # Only the actual output is rounded up, not the expected output: + ("3.0", "2.98"), + ("1e3", "999"), + # The current implementation doesn't understand that numbers inside + # strings shouldn't be treated as numbers: + pytest.param("'3.1416'", "'3.14'", marks=pytest.mark.xfail), # type: ignore + ], + ) + def test_number_non_matches(self, testdir, expression, output): + testdir.maketxtfile( + test_doc=""" + >>> {expression} #doctest: +NUMBER + {output} + """.format( + expression=expression, output=output + ) + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=0, failed=1) + + def test_number_and_allow_unicode(self, testdir): + testdir.maketxtfile( + test_doc=""" + >>> from collections import namedtuple + >>> T = namedtuple('T', 'a b c') + >>> T(a=0.2330000001, b=u'str', c=b'bytes') # doctest: +ALLOW_UNICODE, +ALLOW_BYTES, +NUMBER + T(a=0.233, b=u'str', c='bytes') + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) -class TestDoctestSkips(object): +class TestDoctestSkips: """ If all examples in a doctest are skipped due to the SKIP option, then the tests should be SKIPPED rather than PASSED. (#957) @@ -933,13 +1155,12 @@ class TestDoctestSkips(object): ) -class TestDoctestAutoUseFixtures(object): +class TestDoctestAutoUseFixtures: SCOPES = ["module", "session", "class", "function"] def test_doctest_module_session_fixture(self, testdir): - """Test that session fixtures are initialized for doctest modules (#768) - """ + """Test that session fixtures are initialized for doctest modules (#768).""" # session fixture which changes some global data, which will # be accessed by doctests in a module testdir.makeconftest( @@ -947,7 +1168,7 @@ class TestDoctestAutoUseFixtures(object): import pytest import sys - @pytest.yield_fixture(autouse=True, scope='session') + @pytest.fixture(autouse=True, scope='session') def myfixture(): assert not hasattr(sys, 'pytest_session_data') sys.pytest_session_data = 1 @@ -1041,7 +1262,7 @@ class TestDoctestAutoUseFixtures(object): """ ) result = testdir.runpytest("--doctest-modules") - assert "FAILURES" not in str(result.stdout.str()) + result.stdout.no_fnmatch_line("*FAILURES*") result.stdout.fnmatch_lines(["*=== 1 passed in *"]) @pytest.mark.parametrize("scope", SCOPES) @@ -1073,11 +1294,11 @@ class TestDoctestAutoUseFixtures(object): """ ) result = testdir.runpytest("--doctest-modules") - assert "FAILURES" not in str(result.stdout.str()) + str(result.stdout.no_fnmatch_line("*FAILURES*")) result.stdout.fnmatch_lines(["*=== 1 passed in *"]) -class TestDoctestNamespaceFixture(object): +class TestDoctestNamespaceFixture: SCOPES = ["module", "session", "class", "function"] @@ -1139,7 +1360,7 @@ class TestDoctestNamespaceFixture(object): reprec.assertoutcome(passed=1) -class TestDoctestReportingOption(object): +class TestDoctestReportingOption: def _run_doctest_report(self, testdir, format): testdir.makepyfile( """ @@ -1247,11 +1468,12 @@ class Broken: raise KeyError("This should be an AttributeError") -@pytest.mark.skipif(not hasattr(inspect, "unwrap"), reason="nothing to patch") @pytest.mark.parametrize( # pragma: no branch (lambdas are not called) "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True] ) -def test_warning_on_unwrap_of_broken_object(stop): +def test_warning_on_unwrap_of_broken_object( + stop: Optional[Callable[[object], object]] +) -> None: bad_instance = Broken() assert inspect.unwrap.__module__ == "inspect" with _patch_unwrap_mock_aware(): @@ -1260,5 +1482,29 @@ def test_warning_on_unwrap_of_broken_object(stop): pytest.PytestWarning, match="^Got KeyError.* when unwrapping" ): with pytest.raises(KeyError): - inspect.unwrap(bad_instance, stop=stop) + inspect.unwrap(bad_instance, stop=stop) # type: ignore[arg-type] assert inspect.unwrap.__module__ == "inspect" + + +def test_is_setup_py_not_named_setup_py(tmpdir): + not_setup_py = tmpdir.join("not_setup.py") + not_setup_py.write('from setuptools import setup; setup(name="foo")') + assert not _is_setup_py(not_setup_py) + + +@pytest.mark.parametrize("mod", ("setuptools", "distutils.core")) +def test_is_setup_py_is_a_setup_py(tmpdir, mod): + setup_py = tmpdir.join("setup.py") + setup_py.write('from {} import setup; setup(name="foo")'.format(mod)) + assert _is_setup_py(setup_py) + + +@pytest.mark.parametrize("mod", ("setuptools", "distutils.core")) +def test_is_setup_py_different_encoding(tmpdir, mod): + setup_py = tmpdir.join("setup.py") + contents = ( + "# -*- coding: cp1252 -*-\n" + 'from {} import setup; setup(name="foo", description="€")\n'.format(mod) + ) + setup_py.write_binary(contents.encode("cp1252")) + assert _is_setup_py(setup_py) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py index 95ebc415b15..5d003127363 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_entry_points.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - from _pytest.compat import importlib_metadata diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py new file mode 100644 index 00000000000..2857df83236 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_error_diffs.py @@ -0,0 +1,282 @@ +""" +Tests and examples for correct "+/-" usage in error diffs. + +See https://github.com/pytest-dev/pytest/issues/3333 for details. + +""" +import sys + +import pytest + + +TESTCASES = [ + pytest.param( + """ + def test_this(): + result = [1, 4, 3] + expected = [1, 2, 3] + assert result == expected + """, + """ + > assert result == expected + E assert [1, 4, 3] == [1, 2, 3] + E At index 1 diff: 4 != 2 + E Full diff: + E - [1, 2, 3] + E ? ^ + E + [1, 4, 3] + E ? ^ + """, + id="Compare lists, one item differs", + ), + pytest.param( + """ + def test_this(): + result = [1, 2, 3] + expected = [1, 2] + assert result == expected + """, + """ + > assert result == expected + E assert [1, 2, 3] == [1, 2] + E Left contains one more item: 3 + E Full diff: + E - [1, 2] + E + [1, 2, 3] + E ? +++ + """, + id="Compare lists, one extra item", + ), + pytest.param( + """ + def test_this(): + result = [1, 3] + expected = [1, 2, 3] + assert result == expected + """, + """ + > assert result == expected + E assert [1, 3] == [1, 2, 3] + E At index 1 diff: 3 != 2 + E Right contains one more item: 3 + E Full diff: + E - [1, 2, 3] + E ? --- + E + [1, 3] + """, + id="Compare lists, one item missing", + ), + pytest.param( + """ + def test_this(): + result = (1, 4, 3) + expected = (1, 2, 3) + assert result == expected + """, + """ + > assert result == expected + E assert (1, 4, 3) == (1, 2, 3) + E At index 1 diff: 4 != 2 + E Full diff: + E - (1, 2, 3) + E ? ^ + E + (1, 4, 3) + E ? ^ + """, + id="Compare tuples", + ), + pytest.param( + """ + def test_this(): + result = {1, 3, 4} + expected = {1, 2, 3} + assert result == expected + """, + """ + > assert result == expected + E assert {1, 3, 4} == {1, 2, 3} + E Extra items in the left set: + E 4 + E Extra items in the right set: + E 2 + E Full diff: + E - {1, 2, 3} + E ? ^ ^ + E + {1, 3, 4} + E ? ^ ^ + """, + id="Compare sets", + ), + pytest.param( + """ + def test_this(): + result = {1: 'spam', 3: 'eggs'} + expected = {1: 'spam', 2: 'eggs'} + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert {1: 'spam', 3: 'eggs'} == {1: 'spam', 2: 'eggs'} + E Common items: + E {1: 'spam'} + E Left contains 1 more item: + E {3: 'eggs'} + E Right contains 1 more item: + E {2: 'eggs'} + E Full diff: + E - {1: 'spam', 2: 'eggs'} + E ? ^ + E + {1: 'spam', 3: 'eggs'} + E ? ^ + """, + id="Compare dicts with differing keys", + ), + pytest.param( + """ + def test_this(): + result = {1: 'spam', 2: 'eggs'} + expected = {1: 'spam', 2: 'bacon'} + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert {1: 'spam', 2: 'eggs'} == {1: 'spam', 2: 'bacon'} + E Common items: + E {1: 'spam'} + E Differing items: + E {2: 'eggs'} != {2: 'bacon'} + E Full diff: + E - {1: 'spam', 2: 'bacon'} + E ? ^^^^^ + E + {1: 'spam', 2: 'eggs'} + E ? ^^^^ + """, + id="Compare dicts with differing values", + ), + pytest.param( + """ + def test_this(): + result = {1: 'spam', 2: 'eggs'} + expected = {1: 'spam', 3: 'bacon'} + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert {1: 'spam', 2: 'eggs'} == {1: 'spam', 3: 'bacon'} + E Common items: + E {1: 'spam'} + E Left contains 1 more item: + E {2: 'eggs'} + E Right contains 1 more item: + E {3: 'bacon'} + E Full diff: + E - {1: 'spam', 3: 'bacon'} + E ? ^ ^^^^^ + E + {1: 'spam', 2: 'eggs'} + E ? ^ ^^^^ + """, + id="Compare dicts with differing items", + ), + pytest.param( + """ + def test_this(): + result = "spmaeggs" + expected = "spameggs" + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert 'spmaeggs' == 'spameggs' + E - spameggs + E ? - + E + spmaeggs + E ? + + """, + id="Compare strings", + ), + pytest.param( + """ + def test_this(): + result = "spam bacon eggs" + assert "bacon" not in result + """, + """ + > assert "bacon" not in result + E AssertionError: assert 'bacon' not in 'spam bacon eggs' + E 'bacon' is contained here: + E spam bacon eggs + E ? +++++ + """, + id='Test "not in" string', + ), +] +if sys.version_info[:2] >= (3, 7): + TESTCASES.extend( + [ + pytest.param( + """ + from dataclasses import dataclass + + @dataclass + class A: + a: int + b: str + + def test_this(): + result = A(1, 'spam') + expected = A(2, 'spam') + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam') + E Matching attributes: + E ['b'] + E Differing attributes: + E ['a'] + E Drill down into differing attribute a: + E a: 1 != 2 + E +1 + E -2 + """, + id="Compare data classes", + ), + pytest.param( + """ + import attr + + @attr.s(auto_attribs=True) + class A: + a: int + b: str + + def test_this(): + result = A(1, 'spam') + expected = A(1, 'eggs') + assert result == expected + """, + """ + > assert result == expected + E AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs') + E Matching attributes: + E ['a'] + E Differing attributes: + E ['b'] + E Drill down into differing attribute b: + E b: 'spam' != 'eggs' + E - eggs + E + spam + """, + id="Compare attrs classes", + ), + ] + ) + + +@pytest.mark.parametrize("code, expected", TESTCASES) +def test_error_diff(code, expected, testdir): + expected = [line.lstrip() for line in expected.splitlines()] + p = testdir.makepyfile(code) + result = testdir.runpytest(p, "-vv") + result.stdout.fnmatch_lines(expected) + assert result.ret == 1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py new file mode 100644 index 00000000000..87a195bf807 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_faulthandler.py @@ -0,0 +1,136 @@ +import sys + +import pytest + + +def test_enabled(testdir): + """Test single crashing test displays a traceback.""" + testdir.makepyfile( + """ + import faulthandler + def test_crash(): + faulthandler._sigabrt() + """ + ) + result = testdir.runpytest_subprocess() + result.stderr.fnmatch_lines(["*Fatal Python error*"]) + assert result.ret != 0 + + +def test_crash_near_exit(testdir): + """Test that fault handler displays crashes that happen even after + pytest is exiting (for example, when the interpreter is shutting down).""" + testdir.makepyfile( + """ + import faulthandler + import atexit + def test_ok(): + atexit.register(faulthandler._sigabrt) + """ + ) + result = testdir.runpytest_subprocess() + result.stderr.fnmatch_lines(["*Fatal Python error*"]) + assert result.ret != 0 + + +def test_disabled(testdir): + """Test option to disable fault handler in the command line.""" + testdir.makepyfile( + """ + import faulthandler + def test_disabled(): + assert not faulthandler.is_enabled() + """ + ) + result = testdir.runpytest_subprocess("-p", "no:faulthandler") + result.stdout.fnmatch_lines(["*1 passed*"]) + assert result.ret == 0 + + +@pytest.mark.parametrize( + "enabled", + [ + pytest.param( + True, marks=pytest.mark.skip(reason="sometimes crashes on CI (#7022)") + ), + False, + ], +) +def test_timeout(testdir, enabled: bool) -> None: + """Test option to dump tracebacks after a certain timeout. + + If faulthandler is disabled, no traceback will be dumped. + """ + testdir.makepyfile( + """ + import os, time + def test_timeout(): + time.sleep(1 if "CI" in os.environ else 0.1) + """ + ) + testdir.makeini( + """ + [pytest] + faulthandler_timeout = 0.01 + """ + ) + args = ["-p", "no:faulthandler"] if not enabled else [] + + result = testdir.runpytest_subprocess(*args) + tb_output = "most recent call first" + if enabled: + result.stderr.fnmatch_lines(["*%s*" % tb_output]) + else: + assert tb_output not in result.stderr.str() + result.stdout.fnmatch_lines(["*1 passed*"]) + assert result.ret == 0 + + +@pytest.mark.parametrize("hook_name", ["pytest_enter_pdb", "pytest_exception_interact"]) +def test_cancel_timeout_on_hook(monkeypatch, hook_name): + """Make sure that we are cancelling any scheduled traceback dumping due + to timeout before entering pdb (pytest-dev/pytest-faulthandler#12) or any + other interactive exception (pytest-dev/pytest-faulthandler#14).""" + import faulthandler + from _pytest.faulthandler import FaultHandlerHooks + + called = [] + + monkeypatch.setattr( + faulthandler, "cancel_dump_traceback_later", lambda: called.append(1) + ) + + # call our hook explicitly, we can trust that pytest will call the hook + # for us at the appropriate moment + hook_func = getattr(FaultHandlerHooks, hook_name) + hook_func(self=None) + assert called == [1] + + +@pytest.mark.parametrize("faulthandler_timeout", [0, 2]) +def test_already_initialized(faulthandler_timeout, testdir): + """Test for faulthandler being initialized earlier than pytest (#6575).""" + testdir.makepyfile( + """ + def test(): + import faulthandler + assert faulthandler.is_enabled() + """ + ) + result = testdir.run( + sys.executable, + "-X", + "faulthandler", + "-mpytest", + testdir.tmpdir, + "-o", + "faulthandler_timeout={}".format(faulthandler_timeout), + ) + # ensure warning is emitted if faulthandler_timeout is configured + warning_line = "*faulthandler.py*faulthandler module enabled before*" + if faulthandler_timeout > 0: + result.stdout.fnmatch_lines(warning_line) + else: + result.stdout.no_fnmatch_line(warning_line) + result.stdout.fnmatch_lines("*1 passed*") + assert result.ret == 0 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py new file mode 100644 index 00000000000..974dcf8f3cd --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_findpaths.py @@ -0,0 +1,125 @@ +from textwrap import dedent + +import pytest +from _pytest.config.findpaths import get_common_ancestor +from _pytest.config.findpaths import get_dirs_from_args +from _pytest.config.findpaths import load_config_dict_from_file +from _pytest.pathlib import Path + + +class TestLoadConfigDictFromFile: + def test_empty_pytest_ini(self, tmp_path: Path) -> None: + """pytest.ini files are always considered for configuration, even if empty""" + fn = tmp_path / "pytest.ini" + fn.write_text("", encoding="utf-8") + assert load_config_dict_from_file(fn) == {} + + def test_pytest_ini(self, tmp_path: Path) -> None: + """[pytest] section in pytest.ini files is read correctly""" + fn = tmp_path / "pytest.ini" + fn.write_text("[pytest]\nx=1", encoding="utf-8") + assert load_config_dict_from_file(fn) == {"x": "1"} + + def test_custom_ini(self, tmp_path: Path) -> None: + """[pytest] section in any .ini file is read correctly""" + fn = tmp_path / "custom.ini" + fn.write_text("[pytest]\nx=1", encoding="utf-8") + assert load_config_dict_from_file(fn) == {"x": "1"} + + def test_custom_ini_without_section(self, tmp_path: Path) -> None: + """Custom .ini files without [pytest] section are not considered for configuration""" + fn = tmp_path / "custom.ini" + fn.write_text("[custom]", encoding="utf-8") + assert load_config_dict_from_file(fn) is None + + def test_custom_cfg_file(self, tmp_path: Path) -> None: + """Custom .cfg files without [tool:pytest] section are not considered for configuration""" + fn = tmp_path / "custom.cfg" + fn.write_text("[custom]", encoding="utf-8") + assert load_config_dict_from_file(fn) is None + + def test_valid_cfg_file(self, tmp_path: Path) -> None: + """Custom .cfg files with [tool:pytest] section are read correctly""" + fn = tmp_path / "custom.cfg" + fn.write_text("[tool:pytest]\nx=1", encoding="utf-8") + assert load_config_dict_from_file(fn) == {"x": "1"} + + def test_unsupported_pytest_section_in_cfg_file(self, tmp_path: Path) -> None: + """.cfg files with [pytest] section are no longer supported and should fail to alert users""" + fn = tmp_path / "custom.cfg" + fn.write_text("[pytest]", encoding="utf-8") + with pytest.raises(pytest.fail.Exception): + load_config_dict_from_file(fn) + + def test_invalid_toml_file(self, tmp_path: Path) -> None: + """.toml files without [tool.pytest.ini_options] are not considered for configuration.""" + fn = tmp_path / "myconfig.toml" + fn.write_text( + dedent( + """ + [build_system] + x = 1 + """ + ), + encoding="utf-8", + ) + assert load_config_dict_from_file(fn) is None + + def test_valid_toml_file(self, tmp_path: Path) -> None: + """.toml files with [tool.pytest.ini_options] are read correctly, including changing + data types to str/list for compatibility with other configuration options.""" + fn = tmp_path / "myconfig.toml" + fn.write_text( + dedent( + """ + [tool.pytest.ini_options] + x = 1 + y = 20.0 + values = ["tests", "integration"] + name = "foo" + """ + ), + encoding="utf-8", + ) + assert load_config_dict_from_file(fn) == { + "x": "1", + "y": "20.0", + "values": ["tests", "integration"], + "name": "foo", + } + + +class TestCommonAncestor: + def test_has_ancestor(self, tmp_path: Path) -> None: + fn1 = tmp_path / "foo" / "bar" / "test_1.py" + fn1.parent.mkdir(parents=True) + fn1.touch() + fn2 = tmp_path / "foo" / "zaz" / "test_2.py" + fn2.parent.mkdir(parents=True) + fn2.touch() + assert get_common_ancestor([fn1, fn2]) == tmp_path / "foo" + assert get_common_ancestor([fn1.parent, fn2]) == tmp_path / "foo" + assert get_common_ancestor([fn1.parent, fn2.parent]) == tmp_path / "foo" + assert get_common_ancestor([fn1, fn2.parent]) == tmp_path / "foo" + + def test_single_dir(self, tmp_path: Path) -> None: + assert get_common_ancestor([tmp_path]) == tmp_path + + def test_single_file(self, tmp_path: Path) -> None: + fn = tmp_path / "foo.py" + fn.touch() + assert get_common_ancestor([fn]) == tmp_path + + +def test_get_dirs_from_args(tmp_path): + """get_dirs_from_args() skips over non-existing directories and files""" + fn = tmp_path / "foo.py" + fn.touch() + d = tmp_path / "tests" + d.mkdir() + option = "--foobar=/foo.txt" + # xdist uses options in this format for its rsync feature (#7638) + xdist_rsync_option = "popen=c:/dest" + assert get_dirs_from_args( + [str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option] + ) == [fn.parent, d] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py index 75888fe14cb..6116242ec0d 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_helpconfig.py @@ -1,16 +1,11 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import pytest -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.config import ExitCode -def test_version(testdir, pytestconfig): - result = testdir.runpytest("--version") +def test_version_verbose(testdir, pytestconfig): + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") + result = testdir.runpytest("--version", "--version") assert result.ret == 0 - # p = py.path.local(py.__file__).dirpath() result.stderr.fnmatch_lines( ["*pytest*{}*imported from*".format(pytest.__version__)] ) @@ -18,12 +13,23 @@ def test_version(testdir, pytestconfig): result.stderr.fnmatch_lines(["*setuptools registered plugins:", "*at*"]) +def test_version_less_verbose(testdir, pytestconfig): + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") + result = testdir.runpytest("--version") + assert result.ret == 0 + # p = py.path.local(py.__file__).dirpath() + result.stderr.fnmatch_lines(["pytest {}".format(pytest.__version__)]) + + def test_help(testdir): result = testdir.runpytest("--help") assert result.ret == 0 result.stdout.fnmatch_lines( """ - *-v*verbose* + -m MARKEXPR only run tests matching given mark expression. + For example: -m 'mark1 and not mark2'. + reporting: + --durations=N * *setup.cfg* *minversion* *to see*markers*pytest --markers* @@ -32,6 +38,39 @@ def test_help(testdir): ) +def test_none_help_param_raises_exception(testdir): + """Test that a None help param raises a TypeError.""" + testdir.makeconftest( + """ + def pytest_addoption(parser): + parser.addini("test_ini", None, default=True, type="bool") + """ + ) + result = testdir.runpytest("--help") + result.stderr.fnmatch_lines( + ["*TypeError: help argument cannot be None for test_ini*"] + ) + + +def test_empty_help_param(testdir): + """Test that an empty help param is displayed correctly.""" + testdir.makeconftest( + """ + def pytest_addoption(parser): + parser.addini("test_ini", "", default=True, type="bool") + """ + ) + result = testdir.runpytest("--help") + assert result.ret == 0 + lines = [ + " required_plugins (args):", + " plugins that must be present for pytest to run*", + " test_ini (bool):*", + "environment variables:", + ] + result.stdout.fnmatch_lines(lines, consecutive=True) + + def test_hookvalidation_unknown(testdir): testdir.makeconftest( """ @@ -54,7 +93,7 @@ def test_hookvalidation_optional(testdir): """ ) result = testdir.runpytest() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED def test_traceconfig(testdir): @@ -62,9 +101,9 @@ def test_traceconfig(testdir): result.stdout.fnmatch_lines(["*using*pytest*py*", "*active plugins*"]) -def test_debug(testdir, monkeypatch): +def test_debug(testdir): result = testdir.runpytest_subprocess("--debug") - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED p = testdir.tmpdir.join("pytestdebug.log") assert "pytest_sessionstart" in p.read() @@ -72,7 +111,7 @@ def test_debug(testdir, monkeypatch): def test_PYTEST_DEBUG(testdir, monkeypatch): monkeypatch.setenv("PYTEST_DEBUG", "1") result = testdir.runpytest_subprocess() - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stderr.fnmatch_lines( ["*pytest_plugin_registered*", "*manager*PluginManager*"] ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py index ba529cf331d..3cc93a39805 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_junitxml.py @@ -1,26 +1,54 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import platform -import sys from datetime import datetime +from typing import cast +from typing import List +from typing import Tuple from xml.dom import minidom import py +import xmlschema import pytest +from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.junitxml import bin_xml_escape from _pytest.junitxml import LogXML +from _pytest.pathlib import Path from _pytest.reports import BaseReport +from _pytest.reports import TestReport +from _pytest.store import Store + + +@pytest.fixture(scope="session") +def schema(): + """Return an xmlschema.XMLSchema object for the junit-10.xsd file.""" + fn = Path(__file__).parent / "example_scripts/junit-10.xsd" + with fn.open() as f: + return xmlschema.XMLSchema(f) + + +@pytest.fixture +def run_and_parse(testdir, schema): + """Fixture that returns a function that can be used to execute pytest and + return the parsed ``DomNode`` of the root xml node. + + The ``family`` parameter is used to configure the ``junit_family`` of the written report. + "xunit2" is also automatically validated against the schema. + """ + def run(*args, family="xunit1"): + if family: + args = ("-o", "junit_family=" + family) + args + xml_path = testdir.tmpdir.join("junit.xml") + result = testdir.runpytest("--junitxml=%s" % xml_path, *args) + if family == "xunit2": + with xml_path.open() as f: + schema.validate(f) + xmldoc = minidom.parse(str(xml_path)) + return result, DomNode(xmldoc) -def runandparse(testdir, *args): - resultpath = testdir.tmpdir.join("junit.xml") - result = testdir.runpytest("--junitxml=%s" % resultpath, *args) - xmldoc = minidom.parse(str(resultpath)) - return result, DomNode(xmldoc) + return run def assert_attr(node, **kwargs): @@ -36,7 +64,7 @@ def assert_attr(node, **kwargs): assert on_node == expected -class DomNode(object): +class DomNode: def __init__(self, dom): self.__node = dom @@ -97,8 +125,12 @@ class DomNode(object): return type(self)(self.__node.nextSibling) -class TestPython(object): - def test_summing_simple(self, testdir): +parametrize_families = pytest.mark.parametrize("xunit_family", ["xunit1", "xunit2"]) + + +class TestPython: + @parametrize_families + def test_summing_simple(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -116,12 +148,13 @@ class TestPython(object): assert 1 """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5) - def test_summing_simple_with_errors(self, testdir): + @parametrize_families + def test_summing_simple_with_errors(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -142,23 +175,25 @@ class TestPython(object): assert True """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5) - def test_hostname_in_xml(self, testdir): + @parametrize_families + def test_hostname_in_xml(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ def test_pass(): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) node = dom.find_first_by_tag("testsuite") node.assert_attr(hostname=platform.node()) - def test_timestamp_in_xml(self, testdir): + @parametrize_families + def test_timestamp_in_xml(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ def test_pass(): @@ -166,31 +201,33 @@ class TestPython(object): """ ) start_time = datetime.now() - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) node = dom.find_first_by_tag("testsuite") timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f") assert start_time <= timestamp < datetime.now() - def test_timing_function(self, testdir): + def test_timing_function(self, testdir, run_and_parse, mock_timing): testdir.makepyfile( """ - import time, pytest + from _pytest import timing def setup_module(): - time.sleep(0.01) + timing.sleep(1) def teardown_module(): - time.sleep(0.01) + timing.sleep(2) def test_sleep(): - time.sleep(0.01) + timing.sleep(4) """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse() node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") val = tnode["time"] - assert round(float(val), 2) >= 0.03 + assert float(val) == 7.0 @pytest.mark.parametrize("duration_report", ["call", "total"]) - def test_junit_duration_report(self, testdir, monkeypatch, duration_report): + def test_junit_duration_report( + self, testdir, monkeypatch, duration_report, run_and_parse + ): # mock LogXML.node_reporter so it always sets a known duration to each test report object original_node_reporter = LogXML.node_reporter @@ -208,8 +245,8 @@ class TestPython(object): pass """ ) - result, dom = runandparse( - testdir, "-o", "junit_duration_report={}".format(duration_report) + result, dom = run_and_parse( + "-o", "junit_duration_report={}".format(duration_report) ) node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") @@ -220,29 +257,31 @@ class TestPython(object): assert duration_report == "call" assert val == 1.0 - def test_setup_error(self, testdir): + @parametrize_families + def test_setup_error(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @pytest.fixture def arg(request): - raise ValueError() + raise ValueError("Error reason") def test_function(arg): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr(classname="test_setup_error", name="test_function") fnode = tnode.find_first_by_tag("error") - fnode.assert_attr(message="test setup failure") + fnode.assert_attr(message='failed on setup with "ValueError: Error reason"') assert "ValueError" in fnode.toxml() - def test_teardown_error(self, testdir): + @parametrize_families + def test_teardown_error(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -250,21 +289,22 @@ class TestPython(object): @pytest.fixture def arg(): yield - raise ValueError() + raise ValueError('Error reason') def test_function(arg): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") tnode.assert_attr(classname="test_teardown_error", name="test_function") fnode = tnode.find_first_by_tag("error") - fnode.assert_attr(message="test teardown failure") + fnode.assert_attr(message='failed on teardown with "ValueError: Error reason"') assert "ValueError" in fnode.toxml() - def test_call_failure_teardown_error(self, testdir): + @parametrize_families + def test_call_failure_teardown_error(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -277,19 +317,23 @@ class TestPython(object): raise Exception("Call Exception") """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(errors=1, failures=1, tests=1) first, second = dom.find_by_tag("testcase") - if not first or not second or first == second: - assert 0 + assert first + assert second + assert first != second fnode = first.find_first_by_tag("failure") fnode.assert_attr(message="Exception: Call Exception") snode = second.find_first_by_tag("error") - snode.assert_attr(message="test teardown failure") + snode.assert_attr( + message='failed on teardown with "Exception: Teardown Exception"' + ) - def test_skip_contains_name_reason(self, testdir): + @parametrize_families + def test_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -297,7 +341,7 @@ class TestPython(object): pytest.skip("hello23") """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") node.assert_attr(skipped=1) @@ -306,7 +350,8 @@ class TestPython(object): snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello23") - def test_mark_skip_contains_name_reason(self, testdir): + @parametrize_families + def test_mark_skip_contains_name_reason(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -315,7 +360,7 @@ class TestPython(object): assert True """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") node.assert_attr(skipped=1) @@ -326,7 +371,10 @@ class TestPython(object): snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello24") - def test_mark_skipif_contains_name_reason(self, testdir): + @parametrize_families + def test_mark_skipif_contains_name_reason( + self, testdir, run_and_parse, xunit_family + ): testdir.makepyfile( """ import pytest @@ -336,7 +384,7 @@ class TestPython(object): assert True """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") node.assert_attr(skipped=1) @@ -347,7 +395,10 @@ class TestPython(object): snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello25") - def test_mark_skip_doesnt_capture_output(self, testdir): + @parametrize_families + def test_mark_skip_doesnt_capture_output( + self, testdir, run_and_parse, xunit_family + ): testdir.makepyfile( """ import pytest @@ -356,12 +407,13 @@ class TestPython(object): print("bar!") """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 node_xml = dom.find_first_by_tag("testsuite").toxml() assert "bar!" not in node_xml - def test_classname_instance(self, testdir): + @parametrize_families + def test_classname_instance(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ class TestClass(object): @@ -369,7 +421,7 @@ class TestPython(object): assert 0 """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=1) @@ -378,20 +430,22 @@ class TestPython(object): classname="test_classname_instance.TestClass", name="test_method" ) - def test_classname_nested_dir(self, testdir): + @parametrize_families + def test_classname_nested_dir(self, testdir, run_and_parse, xunit_family): p = testdir.tmpdir.ensure("sub", "test_hello.py") p.write("def test_func(): 0/0") - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr(classname="sub.test_hello", name="test_func") - def test_internal_error(self, testdir): + @parametrize_families + def test_internal_error(self, testdir, run_and_parse, xunit_family): testdir.makeconftest("def pytest_runtest_protocol(): 0 / 0") testdir.makepyfile("def test_function(): pass") - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) @@ -401,8 +455,13 @@ class TestPython(object): fnode.assert_attr(message="internal error") assert "Division" in fnode.toxml() - @pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) - def test_failure_function(self, testdir, junit_logging): + @pytest.mark.parametrize( + "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"] + ) + @parametrize_families + def test_failure_function( + self, testdir, junit_logging, run_and_parse, xunit_family + ): testdir.makepyfile( """ import logging @@ -417,35 +476,54 @@ class TestPython(object): """ ) - result, dom = runandparse(testdir, "-o", "junit_logging=%s" % junit_logging) - assert result.ret + result, dom = run_and_parse( + "-o", "junit_logging=%s" % junit_logging, family=xunit_family + ) + assert result.ret, "Expected ret > 0" node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr(classname="test_failure_function", name="test_fail") fnode = tnode.find_first_by_tag("failure") fnode.assert_attr(message="ValueError: 42") - assert "ValueError" in fnode.toxml() - systemout = fnode.next_sibling - assert systemout.tag == "system-out" - assert "hello-stdout" in systemout.toxml() - assert "info msg" not in systemout.toxml() - systemerr = systemout.next_sibling - assert systemerr.tag == "system-err" - assert "hello-stderr" in systemerr.toxml() - assert "info msg" not in systemerr.toxml() - - if junit_logging == "system-out": - assert "warning msg" in systemout.toxml() - assert "warning msg" not in systemerr.toxml() - elif junit_logging == "system-err": - assert "warning msg" not in systemout.toxml() - assert "warning msg" in systemerr.toxml() - elif junit_logging == "no": - assert "warning msg" not in systemout.toxml() - assert "warning msg" not in systemerr.toxml() - - def test_failure_verbose_message(self, testdir): + assert "ValueError" in fnode.toxml(), "ValueError not included" + + if junit_logging in ["log", "all"]: + logdata = tnode.find_first_by_tag("system-out") + log_xml = logdata.toxml() + assert logdata.tag == "system-out", "Expected tag: system-out" + assert "info msg" not in log_xml, "Unexpected INFO message" + assert "warning msg" in log_xml, "Missing WARN message" + if junit_logging in ["system-out", "out-err", "all"]: + systemout = tnode.find_first_by_tag("system-out") + systemout_xml = systemout.toxml() + assert systemout.tag == "system-out", "Expected tag: system-out" + assert "info msg" not in systemout_xml, "INFO message found in system-out" + assert ( + "hello-stdout" in systemout_xml + ), "Missing 'hello-stdout' in system-out" + if junit_logging in ["system-err", "out-err", "all"]: + systemerr = tnode.find_first_by_tag("system-err") + systemerr_xml = systemerr.toxml() + assert systemerr.tag == "system-err", "Expected tag: system-err" + assert "info msg" not in systemerr_xml, "INFO message found in system-err" + assert ( + "hello-stderr" in systemerr_xml + ), "Missing 'hello-stderr' in system-err" + assert ( + "warning msg" not in systemerr_xml + ), "WARN message found in system-err" + if junit_logging == "no": + assert not tnode.find_by_tag("log"), "Found unexpected content: log" + assert not tnode.find_by_tag( + "system-out" + ), "Found unexpected content: system-out" + assert not tnode.find_by_tag( + "system-err" + ), "Found unexpected content: system-err" + + @parametrize_families + def test_failure_verbose_message(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import sys @@ -453,14 +531,14 @@ class TestPython(object): assert 0, "An error" """ ) - - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") fnode = tnode.find_first_by_tag("failure") - fnode.assert_attr(message="AssertionError: An error assert 0") + fnode.assert_attr(message="AssertionError: An error\nassert 0") - def test_failure_escape(self, testdir): + @parametrize_families + def test_failure_escape(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -470,7 +548,9 @@ class TestPython(object): assert 0 """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse( + "-o", "junit_logging=system-out", family=xunit_family + ) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=3, tests=3) @@ -483,9 +563,10 @@ class TestPython(object): ) sysout = tnode.find_first_by_tag("system-out") text = sysout.text - assert text == "%s\n" % char + assert "%s\n" % char in text - def test_junit_prefixing(self, testdir): + @parametrize_families + def test_junit_prefixing(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ def test_func(): @@ -495,7 +576,7 @@ class TestPython(object): pass """ ) - result, dom = runandparse(testdir, "--junitprefix=xyz") + result, dom = run_and_parse("--junitprefix=xyz", family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(failures=1, tests=2) @@ -506,7 +587,8 @@ class TestPython(object): classname="xyz.test_junit_prefixing.TestHello", name="test_hello" ) - def test_xfailure_function(self, testdir): + @parametrize_families + def test_xfailure_function(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -514,7 +596,7 @@ class TestPython(object): pytest.xfail("42") """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert not result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(skipped=1, tests=1) @@ -522,9 +604,9 @@ class TestPython(object): tnode.assert_attr(classname="test_xfailure_function", name="test_xfail") fnode = tnode.find_first_by_tag("skipped") fnode.assert_attr(type="pytest.xfail", message="42") - # assert "ValueError" in fnode.toxml() - def test_xfailure_marker(self, testdir): + @parametrize_families + def test_xfailure_marker(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -533,7 +615,7 @@ class TestPython(object): assert False """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert not result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(skipped=1, tests=1) @@ -542,7 +624,10 @@ class TestPython(object): fnode = tnode.find_first_by_tag("skipped") fnode.assert_attr(type="pytest.xfail", message="42") - def test_xfail_captures_output_once(self, testdir): + @pytest.mark.parametrize( + "junit_logging", ["no", "log", "system-out", "system-err", "out-err", "all"] + ) + def test_xfail_captures_output_once(self, testdir, junit_logging, run_and_parse): testdir.makepyfile( """ import sys @@ -555,13 +640,21 @@ class TestPython(object): assert 0 """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") - assert len(tnode.find_by_tag("system-err")) == 1 - assert len(tnode.find_by_tag("system-out")) == 1 + if junit_logging in ["system-err", "out-err", "all"]: + assert len(tnode.find_by_tag("system-err")) == 1 + else: + assert len(tnode.find_by_tag("system-err")) == 0 - def test_xfailure_xpass(self, testdir): + if junit_logging in ["log", "system-out", "out-err", "all"]: + assert len(tnode.find_by_tag("system-out")) == 1 + else: + assert len(tnode.find_by_tag("system-out")) == 0 + + @parametrize_families + def test_xfailure_xpass(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -570,14 +663,15 @@ class TestPython(object): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) # assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(skipped=0, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass") - def test_xfailure_xpass_strict(self, testdir): + @parametrize_families + def test_xfailure_xpass_strict(self, testdir, run_and_parse, xunit_family): testdir.makepyfile( """ import pytest @@ -586,7 +680,7 @@ class TestPython(object): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) # assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(skipped=0, tests=1) @@ -595,9 +689,10 @@ class TestPython(object): fnode = tnode.find_first_by_tag("failure") fnode.assert_attr(message="[XPASS(strict)] This needs to fail!") - def test_collect_error(self, testdir): + @parametrize_families + def test_collect_error(self, testdir, run_and_parse, xunit_family): testdir.makepyfile("syntax error") - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(errors=1, tests=1) @@ -606,26 +701,25 @@ class TestPython(object): fnode.assert_attr(message="collection failure") assert "SyntaxError" in fnode.toxml() - def test_unicode(self, testdir): + def test_unicode(self, testdir, run_and_parse): value = "hx\xc4\x85\xc4\x87\n" testdir.makepyfile( - """ + """\ # coding: latin1 def test_hello(): print(%r) assert 0 - """ + """ % value ) - result, dom = runandparse(testdir) + result, dom = run_and_parse() assert result.ret == 1 tnode = dom.find_first_by_tag("testcase") fnode = tnode.find_first_by_tag("failure") - if not sys.platform.startswith("java"): - assert "hx" in fnode.toxml() + assert "hx" in fnode.toxml() - def test_assertion_binchars(self, testdir): - """this test did fail when the escaping wasnt strict""" + def test_assertion_binchars(self, testdir, run_and_parse): + """This test did fail when the escaping wasn't strict.""" testdir.makepyfile( """ @@ -636,23 +730,32 @@ class TestPython(object): assert M1 == M2 """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse() print(dom.toxml()) - def test_pass_captures_stdout(self, testdir): + @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) + def test_pass_captures_stdout(self, testdir, run_and_parse, junit_logging): testdir.makepyfile( """ def test_pass(): print('hello-stdout') """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") - systemout = pnode.find_first_by_tag("system-out") - assert "hello-stdout" in systemout.toxml() + if junit_logging == "no": + assert not node.find_by_tag( + "system-out" + ), "system-out should not be generated" + if junit_logging == "system-out": + systemout = pnode.find_first_by_tag("system-out") + assert ( + "hello-stdout" in systemout.toxml() + ), "'hello-stdout' should be in system-out" - def test_pass_captures_stderr(self, testdir): + @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) + def test_pass_captures_stderr(self, testdir, run_and_parse, junit_logging): testdir.makepyfile( """ import sys @@ -660,13 +763,21 @@ class TestPython(object): sys.stderr.write('hello-stderr') """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") - systemout = pnode.find_first_by_tag("system-err") - assert "hello-stderr" in systemout.toxml() - - def test_setup_error_captures_stdout(self, testdir): + if junit_logging == "no": + assert not node.find_by_tag( + "system-err" + ), "system-err should not be generated" + if junit_logging == "system-err": + systemerr = pnode.find_first_by_tag("system-err") + assert ( + "hello-stderr" in systemerr.toxml() + ), "'hello-stderr' should be in system-err" + + @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) + def test_setup_error_captures_stdout(self, testdir, run_and_parse, junit_logging): testdir.makepyfile( """ import pytest @@ -679,13 +790,21 @@ class TestPython(object): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") - systemout = pnode.find_first_by_tag("system-out") - assert "hello-stdout" in systemout.toxml() + if junit_logging == "no": + assert not node.find_by_tag( + "system-out" + ), "system-out should not be generated" + if junit_logging == "system-out": + systemout = pnode.find_first_by_tag("system-out") + assert ( + "hello-stdout" in systemout.toxml() + ), "'hello-stdout' should be in system-out" - def test_setup_error_captures_stderr(self, testdir): + @pytest.mark.parametrize("junit_logging", ["no", "system-err"]) + def test_setup_error_captures_stderr(self, testdir, run_and_parse, junit_logging): testdir.makepyfile( """ import sys @@ -699,13 +818,21 @@ class TestPython(object): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") - systemout = pnode.find_first_by_tag("system-err") - assert "hello-stderr" in systemout.toxml() - - def test_avoid_double_stdout(self, testdir): + if junit_logging == "no": + assert not node.find_by_tag( + "system-err" + ), "system-err should not be generated" + if junit_logging == "system-err": + systemerr = pnode.find_first_by_tag("system-err") + assert ( + "hello-stderr" in systemerr.toxml() + ), "'hello-stderr' should be in system-err" + + @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) + def test_avoid_double_stdout(self, testdir, run_and_parse, junit_logging): testdir.makepyfile( """ import sys @@ -720,12 +847,17 @@ class TestPython(object): sys.stdout.write('hello-stdout call') """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") - systemout = pnode.find_first_by_tag("system-out") - assert "hello-stdout call" in systemout.toxml() - assert "hello-stdout teardown" in systemout.toxml() + if junit_logging == "no": + assert not node.find_by_tag( + "system-out" + ), "system-out should not be generated" + if junit_logging == "system-out": + systemout = pnode.find_first_by_tag("system-out") + assert "hello-stdout call" in systemout.toxml() + assert "hello-stdout teardown" in systemout.toxml() def test_mangle_test_address(): @@ -736,44 +868,46 @@ def test_mangle_test_address(): assert newnames == ["a.my.py.thing", "Class", "method", "[a-1-::]"] -def test_dont_configure_on_slaves(tmpdir): - gotten = [] +def test_dont_configure_on_workers(tmpdir) -> None: + gotten = [] # type: List[object] + + class FakeConfig: + if TYPE_CHECKING: + workerinput = None - class FakeConfig(object): def __init__(self): self.pluginmanager = self self.option = self + self._store = Store() def getini(self, name): return "pytest" junitprefix = None - # XXX: shouldnt need tmpdir ? + # XXX: shouldn't need tmpdir ? xmlpath = str(tmpdir.join("junix.xml")) register = gotten.append - fake_config = FakeConfig() + fake_config = cast(Config, FakeConfig()) from _pytest import junitxml junitxml.pytest_configure(fake_config) assert len(gotten) == 1 - FakeConfig.slaveinput = None + FakeConfig.workerinput = None junitxml.pytest_configure(fake_config) assert len(gotten) == 1 -class TestNonPython(object): - def test_summing_simple(self, testdir): +class TestNonPython: + @parametrize_families + def test_summing_simple(self, testdir, run_and_parse, xunit_family): testdir.makeconftest( """ import pytest def pytest_collect_file(path, parent): if path.ext == ".xyz": - return MyItem(path, parent) + return MyItem.from_parent(name=path.basename, parent=parent) class MyItem(pytest.Item): - def __init__(self, path, parent): - super(MyItem, self).__init__(path.basename, parent) - self.fspath = path def runtest(self): raise ValueError(42) def repr_failure(self, excinfo): @@ -781,7 +915,7 @@ class TestNonPython(object): """ ) testdir.tmpdir.join("myfile.xyz").write("hello") - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") node.assert_attr(errors=0, failures=1, skipped=0, tests=1) @@ -792,7 +926,8 @@ class TestNonPython(object): assert "custom item runtest failed" in fnode.toxml() -def test_nullbyte(testdir): +@pytest.mark.parametrize("junit_logging", ["no", "system-out"]) +def test_nullbyte(testdir, junit_logging): # A null byte can not occur in XML (see section 2.2 of the spec) testdir.makepyfile( """ @@ -804,13 +939,17 @@ def test_nullbyte(testdir): """ ) xmlf = testdir.tmpdir.join("junit.xml") - testdir.runpytest("--junitxml=%s" % xmlf) + testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) text = xmlf.read() assert "\x00" not in text - assert "#x00" in text + if junit_logging == "system-out": + assert "#x00" in text + if junit_logging == "no": + assert "#x00" not in text -def test_nullbyte_replace(testdir): +@pytest.mark.parametrize("junit_logging", ["no", "system-out"]) +def test_nullbyte_replace(testdir, junit_logging): # Check if the null byte gets replaced testdir.makepyfile( """ @@ -822,25 +961,23 @@ def test_nullbyte_replace(testdir): """ ) xmlf = testdir.tmpdir.join("junit.xml") - testdir.runpytest("--junitxml=%s" % xmlf) + testdir.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) text = xmlf.read() - assert "#x0" in text + if junit_logging == "system-out": + assert "#x0" in text + if junit_logging == "no": + assert "#x0" not in text def test_invalid_xml_escape(): # Test some more invalid xml chars, the full range should be - # tested really but let's just thest the edges of the ranges - # intead. + # tested really but let's just test the edges of the ranges + # instead. # XXX This only tests low unicode character points for now as # there are some issues with the testing infrastructure for # the higher ones. # XXX Testing 0xD (\r) is tricky as it overwrites the just written # line in the output, so we skip it too. - global unichr - try: - unichr(65) - except NameError: - unichr = chr invalid = ( 0x00, 0x1, @@ -857,17 +994,15 @@ def test_invalid_xml_escape(): valid = (0x9, 0xA, 0x20) # 0xD, 0xD7FF, 0xE000, 0xFFFD, 0x10000, 0x10FFFF) - from _pytest.junitxml import bin_xml_escape - for i in invalid: - got = bin_xml_escape(unichr(i)).uniobj + got = bin_xml_escape(chr(i)) if i <= 0xFF: expected = "#x%02X" % i else: expected = "#x%04X" % i assert got == expected for i in valid: - assert chr(i) == bin_xml_escape(unichr(i)).uniobj + assert chr(i) == bin_xml_escape(chr(i)) def test_logxml_path_expansion(tmpdir, monkeypatch): @@ -914,22 +1049,22 @@ def test_logxml_check_isdir(testdir): result.stderr.fnmatch_lines(["*--junitxml must be a filename*"]) -def test_escaped_parametrized_names_xml(testdir): +def test_escaped_parametrized_names_xml(testdir, run_and_parse): testdir.makepyfile( - """ + """\ import pytest - @pytest.mark.parametrize('char', [u"\\x00"]) + @pytest.mark.parametrize('char', ["\\x00"]) def test_func(char): assert char - """ + """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse() assert result.ret == 0 node = dom.find_first_by_tag("testcase") node.assert_attr(name="test_func[\\x00]") -def test_double_colon_split_function_issue469(testdir): +def test_double_colon_split_function_issue469(testdir, run_and_parse): testdir.makepyfile( """ import pytest @@ -938,14 +1073,14 @@ def test_double_colon_split_function_issue469(testdir): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse() assert result.ret == 0 node = dom.find_first_by_tag("testcase") node.assert_attr(classname="test_double_colon_split_function_issue469") node.assert_attr(name="test_func[double::colon]") -def test_double_colon_split_method_issue469(testdir): +def test_double_colon_split_method_issue469(testdir, run_and_parse): testdir.makepyfile( """ import pytest @@ -955,25 +1090,25 @@ def test_double_colon_split_method_issue469(testdir): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse() assert result.ret == 0 node = dom.find_first_by_tag("testcase") node.assert_attr(classname="test_double_colon_split_method_issue469.TestClass") node.assert_attr(name="test_func[double::colon]") -def test_unicode_issue368(testdir): +def test_unicode_issue368(testdir) -> None: path = testdir.tmpdir.join("test.xml") log = LogXML(str(path), None) - ustr = u"ВНИ!" + ustr = "ВНИ!" class Report(BaseReport): longrepr = ustr - sections = [] + sections = [] # type: List[Tuple[str, str]] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" - test_report = Report() + test_report = cast(TestReport, Report()) # hopefully this is not too brittle ... log.pytest_sessionstart() @@ -986,12 +1121,12 @@ def test_unicode_issue368(testdir): node_reporter.append_skipped(test_report) test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣" node_reporter.append_skipped(test_report) - test_report.wasxfail = ustr + test_report.wasxfail = ustr # type: ignore[attr-defined] node_reporter.append_skipped(test_report) log.pytest_sessionfinish() -def test_record_property(testdir): +def test_record_property(testdir, run_and_parse): testdir.makepyfile( """ import pytest @@ -1003,16 +1138,17 @@ def test_record_property(testdir): record_property("foo", "<1"); """ ) - result, dom = runandparse(testdir, "-rwv") + result, dom = run_and_parse() node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") psnode = tnode.find_first_by_tag("properties") pnodes = psnode.find_by_tag("property") pnodes[0].assert_attr(name="bar", value="1") pnodes[1].assert_attr(name="foo", value="<1") + result.stdout.fnmatch_lines(["*= 1 passed in *"]) -def test_record_property_same_name(testdir): +def test_record_property_same_name(testdir, run_and_parse): testdir.makepyfile( """ def test_record_with_same_name(record_property): @@ -1020,7 +1156,7 @@ def test_record_property_same_name(testdir): record_property("foo", "baz") """ ) - result, dom = runandparse(testdir, "-rw") + result, dom = run_and_parse() node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") psnode = tnode.find_first_by_tag("properties") @@ -1044,7 +1180,7 @@ def test_record_fixtures_without_junitxml(testdir, fixture_name): @pytest.mark.filterwarnings("default") -def test_record_attribute(testdir): +def test_record_attribute(testdir, run_and_parse): testdir.makeini( """ [pytest] @@ -1062,7 +1198,7 @@ def test_record_attribute(testdir): record_xml_attribute("foo", "<1"); """ ) - result, dom = runandparse(testdir, "-rw") + result, dom = run_and_parse() node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") tnode.assert_attr(bar="1") @@ -1074,9 +1210,8 @@ def test_record_attribute(testdir): @pytest.mark.filterwarnings("default") @pytest.mark.parametrize("fixture_name", ["record_xml_attribute", "record_property"]) -def test_record_fixtures_xunit2(testdir, fixture_name): - """Ensure record_xml_attribute and record_property drop values when outside of legacy family - """ +def test_record_fixtures_xunit2(testdir, fixture_name, run_and_parse): + """Ensure record_xml_attribute and record_property drop values when outside of legacy family.""" testdir.makeini( """ [pytest] @@ -1097,7 +1232,7 @@ def test_record_fixtures_xunit2(testdir, fixture_name): ) ) - result, dom = runandparse(testdir, "-rw") + result, dom = run_and_parse(family=None) expected_lines = [] if fixture_name == "record_xml_attribute": expected_lines.append( @@ -1112,11 +1247,10 @@ def test_record_fixtures_xunit2(testdir, fixture_name): result.stdout.fnmatch_lines(expected_lines) -def test_random_report_log_xdist(testdir, monkeypatch): - """xdist calls pytest_runtest_logreport as they are executed by the slaves, +def test_random_report_log_xdist(testdir, monkeypatch, run_and_parse): + """`xdist` calls pytest_runtest_logreport as they are executed by the workers, with nodes from several nodes overlapping, so junitxml must cope with that - to produce correct reports. #1064 - """ + to produce correct reports (#1064).""" pytest.importorskip("xdist") monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) testdir.makepyfile( @@ -1127,7 +1261,7 @@ def test_random_report_log_xdist(testdir, monkeypatch): assert i != 22 """ ) - _, dom = runandparse(testdir, "-n2") + _, dom = run_and_parse("-n2") suite_node = dom.find_first_by_tag("testsuite") failed = [] for case_node in suite_node.find_by_tag("testcase"): @@ -1137,21 +1271,22 @@ def test_random_report_log_xdist(testdir, monkeypatch): assert failed == ["test_x[22]"] -def test_root_testsuites_tag(testdir): +@parametrize_families +def test_root_testsuites_tag(testdir, run_and_parse, xunit_family): testdir.makepyfile( """ def test_x(): pass """ ) - _, dom = runandparse(testdir) + _, dom = run_and_parse(family=xunit_family) root = dom.get_unique_child assert root.tag == "testsuites" suite_node = root.get_unique_child assert suite_node.tag == "testsuite" -def test_runs_twice(testdir): +def test_runs_twice(testdir, run_and_parse): f = testdir.makepyfile( """ def test_pass(): @@ -1159,15 +1294,15 @@ def test_runs_twice(testdir): """ ) - result, dom = runandparse(testdir, f, f) - assert "INTERNALERROR" not in result.stdout.str() + result, dom = run_and_parse(f, f) + result.stdout.no_fnmatch_line("*INTERNALERROR*") first, second = [x["classname"] for x in dom.find_by_tag("testcase")] assert first == second -@pytest.mark.xfail(reason="hangs", run=False) -def test_runs_twice_xdist(testdir): +def test_runs_twice_xdist(testdir, run_and_parse): pytest.importorskip("xdist") + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") f = testdir.makepyfile( """ def test_pass(): @@ -1175,13 +1310,13 @@ def test_runs_twice_xdist(testdir): """ ) - result, dom = runandparse(testdir, f, "--dist", "each", "--tx", "2*popen") - assert "INTERNALERROR" not in result.stdout.str() + result, dom = run_and_parse(f, "--dist", "each", "--tx", "2*popen") + result.stdout.no_fnmatch_line("*INTERNALERROR*") first, second = [x["classname"] for x in dom.find_by_tag("testcase")] assert first == second -def test_fancy_items_regression(testdir): +def test_fancy_items_regression(testdir, run_and_parse): # issue 1259 testdir.makeconftest( """ @@ -1196,14 +1331,14 @@ def test_fancy_items_regression(testdir): class FunCollector(pytest.File): def collect(self): return [ - FunItem('a', self), - NoFunItem('a', self), - NoFunItem('b', self), + FunItem.from_parent(name='a', parent=self), + NoFunItem.from_parent(name='a', parent=self), + NoFunItem.from_parent(name='b', parent=self), ] def pytest_collect_file(path, parent): if path.check(ext='.py'): - return FunCollector(path, parent) + return FunCollector.from_parent(fspath=path, parent=parent) """ ) @@ -1214,36 +1349,37 @@ def test_fancy_items_regression(testdir): """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse() - assert "INTERNALERROR" not in result.stdout.str() + result.stdout.no_fnmatch_line("*INTERNALERROR*") items = sorted("%(classname)s %(name)s" % x for x in dom.find_by_tag("testcase")) import pprint pprint.pprint(items) assert items == [ - u"conftest a", - u"conftest a", - u"conftest b", - u"test_fancy_items_regression a", - u"test_fancy_items_regression a", - u"test_fancy_items_regression b", - u"test_fancy_items_regression test_pass", + "conftest a", + "conftest a", + "conftest b", + "test_fancy_items_regression a", + "test_fancy_items_regression a", + "test_fancy_items_regression b", + "test_fancy_items_regression test_pass", ] -def test_global_properties(testdir): +@parametrize_families +def test_global_properties(testdir, xunit_family) -> None: path = testdir.tmpdir.join("test_global_properties.xml") - log = LogXML(str(path), None) + log = LogXML(str(path), None, family=xunit_family) class Report(BaseReport): - sections = [] + sections = [] # type: List[Tuple[str, str]] nodeid = "test_node_id" log.pytest_sessionstart() - log.add_global_property("foo", 1) - log.add_global_property("bar", 2) + log.add_global_property("foo", "1") + log.add_global_property("bar", "2") log.pytest_sessionfinish() dom = minidom.parse(str(path)) @@ -1267,19 +1403,19 @@ def test_global_properties(testdir): assert actual == expected -def test_url_property(testdir): +def test_url_property(testdir) -> None: test_url = "http://www.github.com/pytest-dev" path = testdir.tmpdir.join("test_url_property.xml") log = LogXML(str(path), None) class Report(BaseReport): longrepr = "FooBarBaz" - sections = [] + sections = [] # type: List[Tuple[str, str]] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" url = test_url - test_report = Report() + test_report = cast(TestReport, Report()) log.pytest_sessionstart() node_reporter = log._opentestcase(test_report) @@ -1293,7 +1429,8 @@ def test_url_property(testdir): ), "The URL did not get written to the xml" -def test_record_testsuite_property(testdir): +@parametrize_families +def test_record_testsuite_property(testdir, run_and_parse, xunit_family): testdir.makepyfile( """ def test_func1(record_testsuite_property): @@ -1303,7 +1440,7 @@ def test_record_testsuite_property(testdir): record_testsuite_property("stats", 10) """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") properties_node = node.find_first_by_tag("properties") @@ -1341,14 +1478,16 @@ def test_record_testsuite_property_type_checking(testdir, junit): @pytest.mark.parametrize("suite_name", ["my_suite", ""]) -def test_set_suite_name(testdir, suite_name): +@parametrize_families +def test_set_suite_name(testdir, suite_name, run_and_parse, xunit_family): if suite_name: testdir.makeini( """ [pytest] - junit_suite_name={} + junit_suite_name={suite_name} + junit_family={family} """.format( - suite_name + suite_name=suite_name, family=xunit_family ) ) expected = suite_name @@ -1362,13 +1501,13 @@ def test_set_suite_name(testdir, suite_name): pass """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 node = dom.find_first_by_tag("testsuite") node.assert_attr(name=expected) -def test_escaped_skipreason_issue3533(testdir): +def test_escaped_skipreason_issue3533(testdir, run_and_parse): testdir.makepyfile( """ import pytest @@ -1377,20 +1516,26 @@ def test_escaped_skipreason_issue3533(testdir): pass """ ) - _, dom = runandparse(testdir) + _, dom = run_and_parse() node = dom.find_first_by_tag("testcase") snode = node.find_first_by_tag("skipped") assert "1 <> 2" in snode.text snode.assert_attr(message="1 <> 2") -def test_logging_passing_tests_disabled_does_not_log_test_output(testdir): +@parametrize_families +def test_logging_passing_tests_disabled_does_not_log_test_output( + testdir, run_and_parse, xunit_family +): testdir.makeini( """ [pytest] junit_log_passing_tests=False junit_logging=system-out - """ + junit_family={family} + """.format( + family=xunit_family + ) ) testdir.makepyfile( """ @@ -1404,22 +1549,26 @@ def test_logging_passing_tests_disabled_does_not_log_test_output(testdir): logging.warning('hello') """ ) - result, dom = runandparse(testdir) + result, dom = run_and_parse(family=xunit_family) assert result.ret == 0 node = dom.find_first_by_tag("testcase") assert len(node.find_by_tag("system-err")) == 0 assert len(node.find_by_tag("system-out")) == 0 +@parametrize_families @pytest.mark.parametrize("junit_logging", ["no", "system-out", "system-err"]) def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( - testdir, junit_logging + testdir, junit_logging, run_and_parse, xunit_family ): testdir.makeini( """ [pytest] junit_log_passing_tests=False - """ + junit_family={family} + """.format( + family=xunit_family + ) ) testdir.makepyfile( """ @@ -1432,7 +1581,9 @@ def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430( assert 0 """ ) - result, dom = runandparse(testdir, "-o", "junit_logging=%s" % junit_logging) + result, dom = run_and_parse( + "-o", "junit_logging=%s" % junit_logging, family=xunit_family + ) assert result.ret == 1 node = dom.find_first_by_tag("testcase") if junit_logging == "system-out": diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py new file mode 100644 index 00000000000..f43f7ded567 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_link_resolve.py @@ -0,0 +1,83 @@ +import os.path +import subprocess +import sys +import textwrap +from contextlib import contextmanager +from string import ascii_lowercase + +import py.path + +from _pytest import pytester + + +@contextmanager +def subst_path_windows(filename): + for c in ascii_lowercase[7:]: # Create a subst drive from H-Z. + c += ":" + if not os.path.exists(c): + drive = c + break + else: + raise AssertionError("Unable to find suitable drive letter for subst.") + + directory = filename.dirpath() + basename = filename.basename + + args = ["subst", drive, str(directory)] + subprocess.check_call(args) + assert os.path.exists(drive) + try: + filename = py.path.local(drive) / basename + yield filename + finally: + args = ["subst", "/D", drive] + subprocess.check_call(args) + + +@contextmanager +def subst_path_linux(filename): + directory = filename.dirpath() + basename = filename.basename + + target = directory / ".." / "sub2" + os.symlink(str(directory), str(target), target_is_directory=True) + try: + filename = target / basename + yield filename + finally: + # We don't need to unlink (it's all in the tempdir). + pass + + +def test_link_resolve(testdir: pytester.Testdir) -> None: + """See: https://github.com/pytest-dev/pytest/issues/5965.""" + sub1 = testdir.mkpydir("sub1") + p = sub1.join("test_foo.py") + p.write( + textwrap.dedent( + """ + import pytest + def test_foo(): + raise AssertionError() + """ + ) + ) + + subst = subst_path_linux + if sys.platform == "win32": + subst = subst_path_windows + + with subst(p) as subst_p: + result = testdir.runpytest(str(subst_p), "-v") + # i.e.: Make sure that the error is reported as a relative path, not as a + # resolved path. + # See: https://github.com/pytest-dev/pytest/issues/5965 + stdout = result.stdout.str() + assert "sub1/test_foo.py" not in stdout + + # i.e.: Expect drive on windows because we just have drive:filename, whereas + # we expect a relative path on Linux. + expect = ( + "*{}*".format(subst_p) if sys.platform == "win32" else "*sub2/test_foo.py*" + ) + result.stdout.fnmatch_lines([expect]) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_main.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_main.py new file mode 100644 index 00000000000..5b45ec6b5bd --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_main.py @@ -0,0 +1,248 @@ +import argparse +import os +import re +from typing import Optional + +import py.path + +import pytest +from _pytest.config import ExitCode +from _pytest.config import UsageError +from _pytest.main import resolve_collection_argument +from _pytest.main import validate_basetemp +from _pytest.pathlib import Path +from _pytest.pytester import Testdir + + +@pytest.mark.parametrize( + "ret_exc", + ( + pytest.param((None, ValueError)), + pytest.param((42, SystemExit)), + pytest.param((False, SystemExit)), + ), +) +def test_wrap_session_notify_exception(ret_exc, testdir): + returncode, exc = ret_exc + c1 = testdir.makeconftest( + """ + import pytest + + def pytest_sessionstart(): + raise {exc}("boom") + + def pytest_internalerror(excrepr, excinfo): + returncode = {returncode!r} + if returncode is not False: + pytest.exit("exiting after %s..." % excinfo.typename, returncode={returncode!r}) + """.format( + returncode=returncode, exc=exc.__name__ + ) + ) + result = testdir.runpytest() + if returncode: + assert result.ret == returncode + else: + assert result.ret == ExitCode.INTERNAL_ERROR + assert result.stdout.lines[0] == "INTERNALERROR> Traceback (most recent call last):" + + if exc == SystemExit: + assert result.stdout.lines[-3:] == [ + 'INTERNALERROR> File "{}", line 4, in pytest_sessionstart'.format(c1), + 'INTERNALERROR> raise SystemExit("boom")', + "INTERNALERROR> SystemExit: boom", + ] + else: + assert result.stdout.lines[-3:] == [ + 'INTERNALERROR> File "{}", line 4, in pytest_sessionstart'.format(c1), + 'INTERNALERROR> raise ValueError("boom")', + "INTERNALERROR> ValueError: boom", + ] + if returncode is False: + assert result.stderr.lines == ["mainloop: caught unexpected SystemExit!"] + else: + assert result.stderr.lines == ["Exit: exiting after {}...".format(exc.__name__)] + + +@pytest.mark.parametrize("returncode", (None, 42)) +def test_wrap_session_exit_sessionfinish( + returncode: Optional[int], testdir: Testdir +) -> None: + testdir.makeconftest( + """ + import pytest + def pytest_sessionfinish(): + pytest.exit(msg="exit_pytest_sessionfinish", returncode={returncode}) + """.format( + returncode=returncode + ) + ) + result = testdir.runpytest() + if returncode: + assert result.ret == returncode + else: + assert result.ret == ExitCode.NO_TESTS_COLLECTED + assert result.stdout.lines[-1] == "collected 0 items" + assert result.stderr.lines == ["Exit: exit_pytest_sessionfinish"] + + +@pytest.mark.parametrize("basetemp", ["foo", "foo/bar"]) +def test_validate_basetemp_ok(tmp_path, basetemp, monkeypatch): + monkeypatch.chdir(str(tmp_path)) + validate_basetemp(tmp_path / basetemp) + + +@pytest.mark.parametrize("basetemp", ["", ".", ".."]) +def test_validate_basetemp_fails(tmp_path, basetemp, monkeypatch): + monkeypatch.chdir(str(tmp_path)) + msg = "basetemp must not be empty, the current working directory or any parent directory of it" + with pytest.raises(argparse.ArgumentTypeError, match=msg): + if basetemp: + basetemp = tmp_path / basetemp + validate_basetemp(basetemp) + + +def test_validate_basetemp_integration(testdir): + result = testdir.runpytest("--basetemp=.") + result.stderr.fnmatch_lines("*basetemp must not be*") + + +class TestResolveCollectionArgument: + @pytest.fixture + def invocation_dir(self, testdir: Testdir) -> py.path.local: + testdir.syspathinsert(str(testdir.tmpdir / "src")) + testdir.chdir() + + pkg = testdir.tmpdir.join("src/pkg").ensure_dir() + pkg.join("__init__.py").ensure() + pkg.join("test.py").ensure() + return testdir.tmpdir + + @pytest.fixture + def invocation_path(self, invocation_dir: py.path.local) -> Path: + return Path(str(invocation_dir)) + + def test_file(self, invocation_dir: py.path.local, invocation_path: Path) -> None: + """File and parts.""" + assert resolve_collection_argument(invocation_path, "src/pkg/test.py") == ( + invocation_dir / "src/pkg/test.py", + [], + ) + assert resolve_collection_argument(invocation_path, "src/pkg/test.py::") == ( + invocation_dir / "src/pkg/test.py", + [""], + ) + assert resolve_collection_argument( + invocation_path, "src/pkg/test.py::foo::bar" + ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"]) + assert resolve_collection_argument( + invocation_path, "src/pkg/test.py::foo::bar::" + ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar", ""]) + + def test_dir(self, invocation_dir: py.path.local, invocation_path: Path) -> None: + """Directory and parts.""" + assert resolve_collection_argument(invocation_path, "src/pkg") == ( + invocation_dir / "src/pkg", + [], + ) + + with pytest.raises( + UsageError, match=r"directory argument cannot contain :: selection parts" + ): + resolve_collection_argument(invocation_path, "src/pkg::") + + with pytest.raises( + UsageError, match=r"directory argument cannot contain :: selection parts" + ): + resolve_collection_argument(invocation_path, "src/pkg::foo::bar") + + def test_pypath(self, invocation_dir: py.path.local, invocation_path: Path) -> None: + """Dotted name and parts.""" + assert resolve_collection_argument( + invocation_path, "pkg.test", as_pypath=True + ) == (invocation_dir / "src/pkg/test.py", []) + assert resolve_collection_argument( + invocation_path, "pkg.test::foo::bar", as_pypath=True + ) == (invocation_dir / "src/pkg/test.py", ["foo", "bar"]) + assert resolve_collection_argument(invocation_path, "pkg", as_pypath=True) == ( + invocation_dir / "src/pkg", + [], + ) + + with pytest.raises( + UsageError, match=r"package argument cannot contain :: selection parts" + ): + resolve_collection_argument( + invocation_path, "pkg::foo::bar", as_pypath=True + ) + + def test_does_not_exist(self, invocation_path: Path) -> None: + """Given a file/module that does not exist raises UsageError.""" + with pytest.raises( + UsageError, match=re.escape("file or directory not found: foobar") + ): + resolve_collection_argument(invocation_path, "foobar") + + with pytest.raises( + UsageError, + match=re.escape( + "module or package not found: foobar (missing __init__.py?)" + ), + ): + resolve_collection_argument(invocation_path, "foobar", as_pypath=True) + + def test_absolute_paths_are_resolved_correctly( + self, invocation_dir: py.path.local, invocation_path: Path + ) -> None: + """Absolute paths resolve back to absolute paths.""" + full_path = str(invocation_dir / "src") + assert resolve_collection_argument(invocation_path, full_path) == ( + py.path.local(os.path.abspath("src")), + [], + ) + + # ensure full paths given in the command-line without the drive letter resolve + # to the full path correctly (#7628) + drive, full_path_without_drive = os.path.splitdrive(full_path) + assert resolve_collection_argument( + invocation_path, full_path_without_drive + ) == (py.path.local(os.path.abspath("src")), []) + + +def test_module_full_path_without_drive(testdir): + """Collect and run test using full path except for the drive letter (#7628). + + Passing a full path without a drive letter would trigger a bug in py.path.local + where it would keep the full path without the drive letter around, instead of resolving + to the full path, resulting in fixtures node ids not matching against test node ids correctly. + """ + testdir.makepyfile( + **{ + "project/conftest.py": """ + import pytest + @pytest.fixture + def fix(): return 1 + """, + } + ) + + testdir.makepyfile( + **{ + "project/tests/dummy_test.py": """ + def test(fix): + assert fix == 1 + """ + } + ) + fn = testdir.tmpdir.join("project/tests/dummy_test.py") + assert fn.isfile() + + drive, path = os.path.splitdrive(str(fn)) + + result = testdir.runpytest(path, "-v") + result.stdout.fnmatch_lines( + [ + os.path.join("project", "tests", "dummy_test.py") + "::test PASSED *", + "* 1 passed in *", + ] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_mark.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_mark.py index 727bd9420c2..5d5e0cf42f7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_mark.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_mark.py @@ -1,55 +1,40 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import sys - -import six +from unittest import mock import pytest -from _pytest.main import EXIT_INTERRUPTED -from _pytest.mark import EMPTY_PARAMETERSET_OPTION +from _pytest.config import ExitCode from _pytest.mark import MarkGenerator as Mark +from _pytest.mark.structures import EMPTY_PARAMETERSET_OPTION from _pytest.nodes import Collector from _pytest.nodes import Node -from _pytest.warning_types import PytestDeprecationWarning -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG - -try: - import mock -except ImportError: - import unittest.mock as mock - -ignore_markinfo = pytest.mark.filterwarnings( - "ignore:MarkInfo objects:pytest.RemovedInPytest4Warning" -) -class TestMark(object): +class TestMark: @pytest.mark.parametrize("attr", ["mark", "param"]) @pytest.mark.parametrize("modulename", ["py.test", "pytest"]) - def test_pytest_exists_in_namespace_all(self, attr, modulename): + def test_pytest_exists_in_namespace_all(self, attr: str, modulename: str) -> None: module = sys.modules[modulename] - assert attr in module.__all__ + assert attr in module.__all__ # type: ignore - def test_pytest_mark_notcallable(self): + def test_pytest_mark_notcallable(self) -> None: mark = Mark() - pytest.raises((AttributeError, TypeError), mark) + with pytest.raises(TypeError): + mark() # type: ignore[operator] def test_mark_with_param(self): def some_function(abc): pass - class SomeClass(object): + class SomeClass: pass assert pytest.mark.foo(some_function) is some_function - assert pytest.mark.foo.with_args(some_function) is not some_function + marked_with_args = pytest.mark.foo.with_args(some_function) + assert marked_with_args is not some_function # type: ignore[comparison-overlap] assert pytest.mark.foo(SomeClass) is SomeClass - assert pytest.mark.foo.with_args(SomeClass) is not SomeClass + assert pytest.mark.foo.with_args(SomeClass) is not SomeClass # type: ignore[comparison-overlap] def test_pytest_mark_name_starts_with_underscore(self): mark = Mark() @@ -57,7 +42,7 @@ class TestMark(object): mark._some_name -def test_marked_class_run_twice(testdir, request): +def test_marked_class_run_twice(testdir): """Test fails file is run twice that contains marked class. See issue#683. """ @@ -213,15 +198,17 @@ def test_strict_prohibits_unregistered_markers(testdir, option_name): @pytest.mark.parametrize( - "spec", + ("expr", "expected_passed"), [ - ("xyz", ("test_one",)), - ("xyz and xyz2", ()), - ("xyz2", ("test_two",)), - ("xyz or xyz2", ("test_one", "test_two")), + ("xyz", ["test_one"]), + ("((( xyz)) )", ["test_one"]), + ("not not xyz", ["test_one"]), + ("xyz and xyz2", []), + ("xyz2", ["test_two"]), + ("xyz or xyz2", ["test_one", "test_two"]), ], ) -def test_mark_option(spec, testdir): +def test_mark_option(expr: str, expected_passed: str, testdir) -> None: testdir.makepyfile( """ import pytest @@ -233,18 +220,17 @@ def test_mark_option(spec, testdir): pass """ ) - opt, passed_result = spec - rec = testdir.inline_run("-m", opt) + rec = testdir.inline_run("-m", expr) passed, skipped, fail = rec.listoutcomes() passed = [x.nodeid.split("::")[-1] for x in passed] - assert len(passed) == len(passed_result) - assert list(passed) == list(passed_result) + assert passed == expected_passed @pytest.mark.parametrize( - "spec", [("interface", ("test_interface",)), ("not interface", ("test_nointer",))] + ("expr", "expected_passed"), + [("interface", ["test_interface"]), ("not interface", ["test_nointer"])], ) -def test_mark_option_custom(spec, testdir): +def test_mark_option_custom(expr: str, expected_passed: str, testdir) -> None: testdir.makeconftest( """ import pytest @@ -262,24 +248,25 @@ def test_mark_option_custom(spec, testdir): pass """ ) - opt, passed_result = spec - rec = testdir.inline_run("-m", opt) + rec = testdir.inline_run("-m", expr) passed, skipped, fail = rec.listoutcomes() passed = [x.nodeid.split("::")[-1] for x in passed] - assert len(passed) == len(passed_result) - assert list(passed) == list(passed_result) + assert passed == expected_passed @pytest.mark.parametrize( - "spec", + ("expr", "expected_passed"), [ - ("interface", ("test_interface",)), - ("not interface", ("test_nointer", "test_pass")), - ("pass", ("test_pass",)), - ("not pass", ("test_interface", "test_nointer")), + ("interface", ["test_interface"]), + ("not interface", ["test_nointer", "test_pass", "test_1", "test_2"]), + ("pass", ["test_pass"]), + ("not pass", ["test_interface", "test_nointer", "test_1", "test_2"]), + ("not not not (pass)", ["test_interface", "test_nointer", "test_1", "test_2"]), + ("1 or 2", ["test_1", "test_2"]), + ("not (1 or 2)", ["test_interface", "test_nointer", "test_pass"]), ], ) -def test_keyword_option_custom(spec, testdir): +def test_keyword_option_custom(expr: str, expected_passed: str, testdir) -> None: testdir.makepyfile( """ def test_interface(): @@ -288,14 +275,16 @@ def test_keyword_option_custom(spec, testdir): pass def test_pass(): pass + def test_1(): + pass + def test_2(): + pass """ ) - opt, passed_result = spec - rec = testdir.inline_run("-k", opt) + rec = testdir.inline_run("-k", expr) passed, skipped, fail = rec.listoutcomes() passed = [x.nodeid.split("::")[-1] for x in passed] - assert len(passed) == len(passed_result) - assert list(passed) == list(passed_result) + assert passed == expected_passed def test_keyword_option_considers_mark(testdir): @@ -306,14 +295,14 @@ def test_keyword_option_considers_mark(testdir): @pytest.mark.parametrize( - "spec", + ("expr", "expected_passed"), [ - ("None", ("test_func[None]",)), - ("1.3", ("test_func[1.3]",)), - ("2-3", ("test_func[2-3]",)), + ("None", ["test_func[None]"]), + ("[1.3]", ["test_func[1.3]"]), + ("2-3", ["test_func[2-3]"]), ], ) -def test_keyword_option_parametrize(spec, testdir): +def test_keyword_option_parametrize(expr: str, expected_passed: str, testdir) -> None: testdir.makepyfile( """ import pytest @@ -322,41 +311,67 @@ def test_keyword_option_parametrize(spec, testdir): pass """ ) - opt, passed_result = spec - rec = testdir.inline_run("-k", opt) + rec = testdir.inline_run("-k", expr) passed, skipped, fail = rec.listoutcomes() passed = [x.nodeid.split("::")[-1] for x in passed] - assert len(passed) == len(passed_result) - assert list(passed) == list(passed_result) + assert passed == expected_passed + + +def test_parametrize_with_module(testdir): + testdir.makepyfile( + """ + import pytest + @pytest.mark.parametrize("arg", [pytest,]) + def test_func(arg): + pass + """ + ) + rec = testdir.inline_run() + passed, skipped, fail = rec.listoutcomes() + expected_id = "test_func[" + pytest.__name__ + "]" + assert passed[0].nodeid.split("::")[-1] == expected_id @pytest.mark.parametrize( - "spec", + ("expr", "expected_error"), [ ( - "foo or import", - "ERROR: Python keyword 'import' not accepted in expressions passed to '-k'", + "foo or", + "at column 7: expected not OR left parenthesis OR identifier; got end of input", + ), + ( + "foo or or", + "at column 8: expected not OR left parenthesis OR identifier; got or", + ), + ("(foo", "at column 5: expected right parenthesis; got end of input",), + ("foo bar", "at column 5: expected end of input; got identifier",), + ( + "or or", + "at column 1: expected not OR left parenthesis OR identifier; got or", + ), + ( + "not or", + "at column 5: expected not OR left parenthesis OR identifier; got or", ), - ("foo or", "ERROR: Wrong expression passed to '-k': foo or"), ], ) -def test_keyword_option_wrong_arguments(spec, testdir, capsys): +def test_keyword_option_wrong_arguments( + expr: str, expected_error: str, testdir, capsys +) -> None: testdir.makepyfile( """ def test_func(arg): pass """ ) - opt, expected_result = spec - testdir.inline_run("-k", opt) - out = capsys.readouterr().err - assert expected_result in out + testdir.inline_run("-k", expr) + err = capsys.readouterr().err + assert expected_error in err def test_parametrized_collected_from_command_line(testdir): - """Parametrized test not collected if test named specified - in command line issue#649. - """ + """Parametrized test not collected if test named specified in command + line issue#649.""" py_file = testdir.makepyfile( """ import pytest @@ -414,7 +429,7 @@ def test_parametrized_with_kwargs(testdir): def test_parametrize_iterator(testdir): - """parametrize should work with generators (#5354).""" + """`parametrize` should work with generators (#5354).""" py_file = testdir.makepyfile( """\ import pytest @@ -435,7 +450,7 @@ def test_parametrize_iterator(testdir): result.stdout.fnmatch_lines(["*3 passed*"]) -class TestFunctional(object): +class TestFunctional: def test_merging_markers_deep(self, testdir): # issue 199 - propagate markers into nested classes p = testdir.makepyfile( @@ -446,7 +461,7 @@ class TestFunctional(object): def test_b(self): assert True class TestC(object): - # this one didnt get marked + # this one didn't get marked def test_d(self): assert True """ @@ -498,7 +513,7 @@ class TestFunctional(object): items, rec = testdir.inline_genitems(p) base_item, sub_item, sub_item_other = items print(items, [x.nodeid for x in items]) - # new api seregates + # new api segregates assert not list(base_item.iter_markers(name="b")) assert not list(sub_item_other.iter_markers(name="b")) assert list(sub_item.iter_markers(name="b")) @@ -534,10 +549,10 @@ class TestFunctional(object): @pytest.mark.c(location="class") class Test: @pytest.mark.c(location="function") - def test_has_own(): + def test_has_own(self): pass - def test_has_inherited(): + def test_has_inherited(self): pass """ @@ -602,18 +617,6 @@ class TestFunctional(object): deselected_tests = dlist[0].items assert len(deselected_tests) == 2 - def test_invalid_m_option(self, testdir): - testdir.makepyfile( - """ - def test_a(): - pass - """ - ) - result = testdir.runpytest("-m bogus/") - result.stdout.fnmatch_lines( - ["INTERNALERROR> Marker expression must be valid Python!"] - ) - def test_keywords_at_node_level(self, testdir): testdir.makepyfile( """ @@ -636,7 +639,6 @@ class TestFunctional(object): reprec = testdir.inline_run() reprec.assertoutcome(passed=1) - @ignore_markinfo def test_keyword_added_for_session(self, testdir): testdir.makeconftest( """ @@ -662,17 +664,16 @@ class TestFunctional(object): assert marker.kwargs == {} """ ) - reprec = testdir.inline_run("-m", "mark1", SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run("-m", "mark1") reprec.assertoutcome(passed=1) def assert_markers(self, items, **expected): - """assert that given items have expected marker names applied to them. - expected should be a dict of (item name -> seq of expected marker names) + """Assert that given items have expected marker names applied to them. + expected should be a dict of (item name -> seq of expected marker names). - .. note:: this could be moved to ``testdir`` if proven to be useful + Note: this could be moved to ``testdir`` if proven to be useful to other modules. """ - items = {x.name: x for x in items} for name, expected_markers in expected.items(): markers = {m.name for m in items[name].iter_markers()} @@ -700,11 +701,41 @@ class TestFunctional(object): assert True """ ) - reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) + reprec = testdir.inline_run() reprec.assertoutcome(skipped=1) + def test_reevaluate_dynamic_expr(self, testdir): + """#7360""" + py_file1 = testdir.makepyfile( + test_reevaluate_dynamic_expr1=""" + import pytest + + skip = True -class TestKeywordSelection(object): + @pytest.mark.skipif("skip") + def test_should_skip(): + assert True + """ + ) + py_file2 = testdir.makepyfile( + test_reevaluate_dynamic_expr2=""" + import pytest + + skip = False + + @pytest.mark.skipif("skip") + def test_should_not_skip(): + assert True + """ + ) + + file_name1 = os.path.basename(py_file1.strpath) + file_name2 = os.path.basename(py_file2.strpath) + reprec = testdir.inline_run(file_name1, file_name2) + reprec.assertoutcome(passed=1, skipped=1) + + +class TestKeywordSelection: def test_select_simple(self, testdir): file_test = testdir.makepyfile( """ @@ -812,10 +843,12 @@ class TestKeywordSelection(object): passed, skipped, failed = reprec.countoutcomes() assert passed + skipped + failed == 0 - def test_no_magic_values(self, testdir): + @pytest.mark.parametrize( + "keyword", ["__", "+", ".."], + ) + def test_no_magic_values(self, testdir, keyword: str) -> None: """Make sure the tests do not match on magic values, - no double underscored values, like '__dict__', - and no instance values, like '()'. + no double underscored values, like '__dict__' and '+'. """ p = testdir.makepyfile( """ @@ -823,19 +856,43 @@ class TestKeywordSelection(object): """ ) - def assert_test_is_not_selected(keyword): - reprec = testdir.inline_run("-k", keyword, p) - passed, skipped, failed = reprec.countoutcomes() - dlist = reprec.getcalls("pytest_deselected") - assert passed + skipped + failed == 0 - deselected_tests = dlist[0].items - assert len(deselected_tests) == 1 + reprec = testdir.inline_run("-k", keyword, p) + passed, skipped, failed = reprec.countoutcomes() + dlist = reprec.getcalls("pytest_deselected") + assert passed + skipped + failed == 0 + deselected_tests = dlist[0].items + assert len(deselected_tests) == 1 + + def test_no_match_directories_outside_the_suite(self, testdir): + """`-k` should not match against directories containing the test suite (#7040).""" + test_contents = """ + def test_aaa(): pass + def test_ddd(): pass + """ + testdir.makepyfile( + **{"ddd/tests/__init__.py": "", "ddd/tests/test_foo.py": test_contents} + ) + + def get_collected_names(*args): + _, rec = testdir.inline_genitems(*args) + calls = rec.getcalls("pytest_collection_finish") + assert len(calls) == 1 + return [x.name for x in calls[0].session.items] - assert_test_is_not_selected("__") - assert_test_is_not_selected("()") + # sanity check: collect both tests in normal runs + assert get_collected_names() == ["test_aaa", "test_ddd"] + # do not collect anything based on names outside the collection tree + assert get_collected_names("-k", testdir.tmpdir.basename) == [] -class TestMarkDecorator(object): + # "-k ddd" should only collect "test_ddd", but not + # 'test_aaa' just because one of its parent directories is named "ddd"; + # this was matched previously because Package.name would contain the full path + # to the package + assert get_collected_names("-k", "ddd") == ["test_ddd"] + + +class TestMarkDecorator: @pytest.mark.parametrize( "lhs, rhs, expected", [ @@ -848,6 +905,12 @@ class TestMarkDecorator(object): def test__eq__(self, lhs, rhs, expected): assert (lhs == rhs) == expected + def test_aliases(self) -> None: + md = pytest.mark.foo(1, "2", three=3) + assert md.name == "foo" + assert md.args == (1, "2") + assert md.kwargs == {"three": 3} + @pytest.mark.parametrize("mark", [None, "", "skip", "xfail"]) def test_parameterset_for_parametrize_marks(testdir, mark): @@ -908,13 +971,13 @@ def test_parameterset_for_fail_at_collect(testdir): result = testdir.runpytest(str(p1)) result.stdout.fnmatch_lines( [ - "collected 0 items / 1 errors", + "collected 0 items / 1 error", "* ERROR collecting test_parameterset_for_fail_at_collect.py *", "Empty parameter set in 'test' at line 3", "*= 1 error in *", ] ) - assert result.ret == EXIT_INTERRUPTED + assert result.ret == ExitCode.INTERRUPTED def test_parameterset_for_parametrize_bad_markname(testdir): @@ -958,7 +1021,11 @@ def test_mark_expressions_no_smear(testdir): def test_addmarker_order(): - node = Node("Test", config=mock.Mock(), session=mock.Mock(), nodeid="Test") + session = mock.Mock() + session.own_markers = [] + session.parent = None + session.nodeid = "" + node = Node.from_parent(session, name="Test") node.add_marker("foo") node.add_marker("bar") node.add_marker("baz", append=False) @@ -971,7 +1038,6 @@ def test_markers_from_parametrize(testdir): """#3605""" testdir.makepyfile( """ - from __future__ import print_function import pytest first_custom_mark = pytest.mark.custom_marker @@ -1001,18 +1067,15 @@ def test_markers_from_parametrize(testdir): """ ) - result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) + result = testdir.runpytest() result.assert_outcomes(passed=4) -def test_pytest_param_id_requires_string(): +def test_pytest_param_id_requires_string() -> None: with pytest.raises(TypeError) as excinfo: - pytest.param(id=True) + pytest.param(id=True) # type: ignore[arg-type] (msg,) = excinfo.value.args - if six.PY2: - assert msg == "Expected id to be a string, got <type 'bool'>: True" - else: - assert msg == "Expected id to be a string, got <class 'bool'>: True" + assert msg == "Expected id to be a string, got <class 'bool'>: True" @pytest.mark.parametrize("s", (None, "hello world")) @@ -1020,13 +1083,18 @@ def test_pytest_param_id_allows_none_or_string(s): assert pytest.param(id=s) -def test_pytest_param_warning_on_unknown_kwargs(): - with pytest.warns(PytestDeprecationWarning) as warninfo: - # typo, should be marks= - pytest.param(1, 2, mark=pytest.mark.xfail()) - assert warninfo[0].filename == __file__ - (msg,) = warninfo[0].message.args - assert msg == ( - "pytest.param() got unexpected keyword arguments: ['mark'].\n" - "This will be an error in future versions." +@pytest.mark.parametrize("expr", ("NOT internal_err", "NOT (internal_err)", "bogus/")) +def test_marker_expr_eval_failure_handling(testdir, expr): + foo = testdir.makepyfile( + """ + import pytest + + @pytest.mark.internal_err + def test_foo(): + pass + """ ) + expected = "ERROR: Wrong expression passed to '-m': {}: *".format(expr) + result = testdir.runpytest(foo, "-m", expr) + result.stderr.fnmatch_lines([expected]) + assert result.ret == ExitCode.USAGE_ERROR diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py new file mode 100644 index 00000000000..faca02d9330 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_mark_expression.py @@ -0,0 +1,169 @@ +from typing import Callable + +import pytest +from _pytest.mark.expression import Expression +from _pytest.mark.expression import ParseError + + +def evaluate(input: str, matcher: Callable[[str], bool]) -> bool: + return Expression.compile(input).evaluate(matcher) + + +def test_empty_is_false() -> None: + assert not evaluate("", lambda ident: False) + assert not evaluate("", lambda ident: True) + assert not evaluate(" ", lambda ident: False) + assert not evaluate("\t", lambda ident: False) + + +@pytest.mark.parametrize( + ("expr", "expected"), + ( + ("true", True), + ("true", True), + ("false", False), + ("not true", False), + ("not false", True), + ("not not true", True), + ("not not false", False), + ("true and true", True), + ("true and false", False), + ("false and true", False), + ("true and true and true", True), + ("true and true and false", False), + ("true and true and not true", False), + ("false or false", False), + ("false or true", True), + ("true or true", True), + ("true or true or false", True), + ("true and true or false", True), + ("not true or true", True), + ("(not true) or true", True), + ("not (true or true)", False), + ("true and true or false and false", True), + ("true and (true or false) and false", False), + ("true and (true or (not (not false))) and false", False), + ), +) +def test_basic(expr: str, expected: bool) -> None: + matcher = {"true": True, "false": False}.__getitem__ + assert evaluate(expr, matcher) is expected + + +@pytest.mark.parametrize( + ("expr", "expected"), + ( + (" true ", True), + (" ((((((true)))))) ", True), + (" ( ((\t (((true))))) \t \t)", True), + ("( true and (((false))))", False), + ("not not not not true", True), + ("not not not not not true", False), + ), +) +def test_syntax_oddeties(expr: str, expected: bool) -> None: + matcher = {"true": True, "false": False}.__getitem__ + assert evaluate(expr, matcher) is expected + + +@pytest.mark.parametrize( + ("expr", "column", "message"), + ( + ("(", 2, "expected not OR left parenthesis OR identifier; got end of input"), + (" (", 3, "expected not OR left parenthesis OR identifier; got end of input",), + ( + ")", + 1, + "expected not OR left parenthesis OR identifier; got right parenthesis", + ), + ( + ") ", + 1, + "expected not OR left parenthesis OR identifier; got right parenthesis", + ), + ("not", 4, "expected not OR left parenthesis OR identifier; got end of input",), + ( + "not not", + 8, + "expected not OR left parenthesis OR identifier; got end of input", + ), + ( + "(not)", + 5, + "expected not OR left parenthesis OR identifier; got right parenthesis", + ), + ("and", 1, "expected not OR left parenthesis OR identifier; got and"), + ( + "ident and", + 10, + "expected not OR left parenthesis OR identifier; got end of input", + ), + ("ident and or", 11, "expected not OR left parenthesis OR identifier; got or",), + ("ident ident", 7, "expected end of input; got identifier"), + ), +) +def test_syntax_errors(expr: str, column: int, message: str) -> None: + with pytest.raises(ParseError) as excinfo: + evaluate(expr, lambda ident: True) + assert excinfo.value.column == column + assert excinfo.value.message == message + + +@pytest.mark.parametrize( + "ident", + ( + ".", + "...", + ":::", + "a:::c", + "a+-b", + "אבגד", + "aaאבגדcc", + "a[bcd]", + "1234", + "1234abcd", + "1234and", + "notandor", + "not_and_or", + "not[and]or", + "1234+5678", + "123.232", + "True", + "False", + "None", + "if", + "else", + "while", + ), +) +def test_valid_idents(ident: str) -> None: + assert evaluate(ident, {ident: True}.__getitem__) + + +@pytest.mark.parametrize( + "ident", + ( + "/", + "\\", + "^", + "*", + "=", + "&", + "%", + "$", + "#", + "@", + "!", + "~", + "{", + "}", + '"', + "'", + "|", + ";", + "←", + ), +) +def test_invalid_idents(ident: str) -> None: + with pytest.raises(ParseError): + evaluate(ident, lambda ident: True) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_meta.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_meta.py new file mode 100644 index 00000000000..1acf6d09f59 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_meta.py @@ -0,0 +1,34 @@ +"""Test importing of all internal packages and modules. + +This ensures all internal packages can be imported without needing the pytest +namespace being set, which is critical for the initialization of xdist. +""" +import pkgutil +import subprocess +import sys +from typing import List + +import _pytest +import pytest + + +def _modules() -> List[str]: + pytest_pkg = _pytest.__path__ # type: str # type: ignore + return sorted( + n + for _, n, _ in pkgutil.walk_packages(pytest_pkg, prefix=_pytest.__name__ + ".") + ) + + +@pytest.mark.slow +@pytest.mark.parametrize("module", _modules()) +def test_no_warnings(module: str) -> None: + # fmt: off + subprocess.check_call(( + sys.executable, + "-W", "error", + # https://github.com/pytest-dev/pytest/issues/5901 + "-W", "ignore:The usage of `cmp` is deprecated and will be removed on or after 2021-06-01. Please use `eq` and `order` instead.:DeprecationWarning", # noqa: E501 + "-c", "__import__({!r})".format(module), + )) + # fmt: on diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_modimport.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_modimport.py deleted file mode 100644 index 5dd057c55e2..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_modimport.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -import subprocess -import sys - -import py - -import _pytest -import pytest - -pytestmark = pytest.mark.slow - -MODSET = [ - x - for x in py.path.local(_pytest.__file__).dirpath().visit("*.py") - if x.purebasename != "__init__" -] - - -@pytest.mark.parametrize("modfile", MODSET, ids=lambda x: x.purebasename) -def test_fileimport(modfile): - # this test ensures all internal packages can import - # without needing the pytest namespace being set - # this is critical for the initialization of xdist - - p = subprocess.Popen( - [ - sys.executable, - "-c", - "import sys, py; py.path.local(sys.argv[1]).pyimport()", - modfile.strpath, - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - (out, err) = p.communicate() - assert p.returncode == 0, "importing %s failed (exitcode %d): out=%r, err=%r" % ( - modfile, - p.returncode, - out, - err, - ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py index 961c57e06a3..fea8a28fba8 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_monkeypatch.py @@ -1,21 +1,23 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import re import sys import textwrap +from typing import Dict +from typing import Generator -import six +import py import pytest +from _pytest.compat import TYPE_CHECKING from _pytest.monkeypatch import MonkeyPatch +from _pytest.pytester import Testdir + +if TYPE_CHECKING: + from typing import Type @pytest.fixture -def mp(): +def mp() -> Generator[MonkeyPatch, None, None]: cwd = os.getcwd() sys_path = list(sys.path) yield MonkeyPatch() @@ -23,14 +25,14 @@ def mp(): os.chdir(cwd) -def test_setattr(): - class A(object): +def test_setattr() -> None: + class A: x = 1 monkeypatch = MonkeyPatch() pytest.raises(AttributeError, monkeypatch.setattr, A, "notexists", 2) monkeypatch.setattr(A, "y", 2, raising=False) - assert A.y == 2 + assert A.y == 2 # type: ignore monkeypatch.undo() assert not hasattr(A, "y") @@ -46,50 +48,54 @@ def test_setattr(): monkeypatch.undo() # double-undo makes no modification assert A.x == 5 + with pytest.raises(TypeError): + monkeypatch.setattr(A, "y") # type: ignore[call-overload] + -class TestSetattrWithImportPath(object): - def test_string_expression(self, monkeypatch): +class TestSetattrWithImportPath: + def test_string_expression(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr("os.path.abspath", lambda x: "hello2") assert os.path.abspath("123") == "hello2" - def test_string_expression_class(self, monkeypatch): + def test_string_expression_class(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr("_pytest.config.Config", 42) import _pytest - assert _pytest.config.Config == 42 + assert _pytest.config.Config == 42 # type: ignore - def test_unicode_string(self, monkeypatch): + def test_unicode_string(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr("_pytest.config.Config", 42) import _pytest - assert _pytest.config.Config == 42 + assert _pytest.config.Config == 42 # type: ignore monkeypatch.delattr("_pytest.config.Config") - def test_wrong_target(self, monkeypatch): - pytest.raises(TypeError, lambda: monkeypatch.setattr(None, None)) + def test_wrong_target(self, monkeypatch: MonkeyPatch) -> None: + with pytest.raises(TypeError): + monkeypatch.setattr(None, None) # type: ignore[call-overload] - def test_unknown_import(self, monkeypatch): - pytest.raises(ImportError, lambda: monkeypatch.setattr("unkn123.classx", None)) + def test_unknown_import(self, monkeypatch: MonkeyPatch) -> None: + with pytest.raises(ImportError): + monkeypatch.setattr("unkn123.classx", None) - def test_unknown_attr(self, monkeypatch): - pytest.raises( - AttributeError, lambda: monkeypatch.setattr("os.path.qweqwe", None) - ) + def test_unknown_attr(self, monkeypatch: MonkeyPatch) -> None: + with pytest.raises(AttributeError): + monkeypatch.setattr("os.path.qweqwe", None) - def test_unknown_attr_non_raising(self, monkeypatch): + def test_unknown_attr_non_raising(self, monkeypatch: MonkeyPatch) -> None: # https://github.com/pytest-dev/pytest/issues/746 monkeypatch.setattr("os.path.qweqwe", 42, raising=False) - assert os.path.qweqwe == 42 + assert os.path.qweqwe == 42 # type: ignore - def test_delattr(self, monkeypatch): + def test_delattr(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.delattr("os.path.abspath") assert not hasattr(os.path, "abspath") monkeypatch.undo() assert os.path.abspath -def test_delattr(): - class A(object): +def test_delattr() -> None: + class A: x = 1 monkeypatch = MonkeyPatch() @@ -108,7 +114,7 @@ def test_delattr(): assert A.x == 1 -def test_setitem(): +def test_setitem() -> None: d = {"x": 1} monkeypatch = MonkeyPatch() monkeypatch.setitem(d, "x", 2) @@ -126,8 +132,8 @@ def test_setitem(): assert d["x"] == 5 -def test_setitem_deleted_meanwhile(): - d = {} +def test_setitem_deleted_meanwhile() -> None: + d = {} # type: Dict[str, object] monkeypatch = MonkeyPatch() monkeypatch.setitem(d, "x", 2) del d["x"] @@ -136,7 +142,7 @@ def test_setitem_deleted_meanwhile(): @pytest.mark.parametrize("before", [True, False]) -def test_setenv_deleted_meanwhile(before): +def test_setenv_deleted_meanwhile(before: bool) -> None: key = "qwpeoip123" if before: os.environ[key] = "world" @@ -151,8 +157,8 @@ def test_setenv_deleted_meanwhile(before): assert key not in os.environ -def test_delitem(): - d = {"x": 1} +def test_delitem() -> None: + d = {"x": 1} # type: Dict[str, object] monkeypatch = MonkeyPatch() monkeypatch.delitem(d, "x") assert "x" not in d @@ -168,10 +174,10 @@ def test_delitem(): assert d == {"hello": "world", "x": 1} -def test_setenv(): +def test_setenv() -> None: monkeypatch = MonkeyPatch() with pytest.warns(pytest.PytestWarning): - monkeypatch.setenv("XYZ123", 2) + monkeypatch.setenv("XYZ123", 2) # type: ignore[arg-type] import os assert os.environ["XYZ123"] == "2" @@ -179,7 +185,7 @@ def test_setenv(): assert "XYZ123" not in os.environ -def test_delenv(): +def test_delenv() -> None: name = "xyz1234" assert name not in os.environ monkeypatch = MonkeyPatch() @@ -200,56 +206,37 @@ def test_delenv(): del os.environ[name] -class TestEnvironWarnings(object): +class TestEnvironWarnings: """ os.environ keys and values should be native strings, otherwise it will cause problems with other modules (notably subprocess). On Python 2 os.environ accepts anything without complaining, while Python 3 does the right thing and raises an error. """ - VAR_NAME = u"PYTEST_INTERNAL_MY_VAR" - - @pytest.mark.skipif(not six.PY2, reason="Python 2 only test") - def test_setenv_unicode_key(self, monkeypatch): - with pytest.warns( - pytest.PytestWarning, - match="Environment variable name {!r} should be str".format(self.VAR_NAME), - ): - monkeypatch.setenv(self.VAR_NAME, "2") - - @pytest.mark.skipif(not six.PY2, reason="Python 2 only test") - def test_delenv_unicode_key(self, monkeypatch): - with pytest.warns( - pytest.PytestWarning, - match="Environment variable name {!r} should be str".format(self.VAR_NAME), - ): - monkeypatch.delenv(self.VAR_NAME, raising=False) - - def test_setenv_non_str_warning(self, monkeypatch): + VAR_NAME = "PYTEST_INTERNAL_MY_VAR" + + def test_setenv_non_str_warning(self, monkeypatch: MonkeyPatch) -> None: value = 2 msg = ( "Value of environment variable PYTEST_INTERNAL_MY_VAR type should be str, " "but got 2 (type: int); converted to str implicitly" ) with pytest.warns(pytest.PytestWarning, match=re.escape(msg)): - monkeypatch.setenv(str(self.VAR_NAME), value) + monkeypatch.setenv(str(self.VAR_NAME), value) # type: ignore[arg-type] -def test_setenv_prepend(): +def test_setenv_prepend() -> None: import os monkeypatch = MonkeyPatch() - with pytest.warns(pytest.PytestWarning): - monkeypatch.setenv("XYZ123", 2, prepend="-") - assert os.environ["XYZ123"] == "2" - with pytest.warns(pytest.PytestWarning): - monkeypatch.setenv("XYZ123", 3, prepend="-") + monkeypatch.setenv("XYZ123", "2", prepend="-") + monkeypatch.setenv("XYZ123", "3", prepend="-") assert os.environ["XYZ123"] == "3-2" monkeypatch.undo() assert "XYZ123" not in os.environ -def test_monkeypatch_plugin(testdir): +def test_monkeypatch_plugin(testdir: Testdir) -> None: reprec = testdir.inline_runsource( """ def test_method(monkeypatch): @@ -260,7 +247,7 @@ def test_monkeypatch_plugin(testdir): assert tuple(res) == (1, 0, 0), res -def test_syspath_prepend(mp): +def test_syspath_prepend(mp: MonkeyPatch) -> None: old = list(sys.path) mp.syspath_prepend("world") mp.syspath_prepend("hello") @@ -272,7 +259,7 @@ def test_syspath_prepend(mp): assert sys.path == old -def test_syspath_prepend_double_undo(mp): +def test_syspath_prepend_double_undo(mp: MonkeyPatch) -> None: old_syspath = sys.path[:] try: mp.syspath_prepend("hello world") @@ -284,24 +271,24 @@ def test_syspath_prepend_double_undo(mp): sys.path[:] = old_syspath -def test_chdir_with_path_local(mp, tmpdir): +def test_chdir_with_path_local(mp: MonkeyPatch, tmpdir: py.path.local) -> None: mp.chdir(tmpdir) assert os.getcwd() == tmpdir.strpath -def test_chdir_with_str(mp, tmpdir): +def test_chdir_with_str(mp: MonkeyPatch, tmpdir: py.path.local) -> None: mp.chdir(tmpdir.strpath) assert os.getcwd() == tmpdir.strpath -def test_chdir_undo(mp, tmpdir): +def test_chdir_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None: cwd = os.getcwd() mp.chdir(tmpdir) mp.undo() assert os.getcwd() == cwd -def test_chdir_double_undo(mp, tmpdir): +def test_chdir_double_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None: mp.chdir(tmpdir.strpath) mp.undo() tmpdir.chdir() @@ -309,7 +296,7 @@ def test_chdir_double_undo(mp, tmpdir): assert os.getcwd() == tmpdir.strpath -def test_issue185_time_breaks(testdir): +def test_issue185_time_breaks(testdir: Testdir) -> None: testdir.makepyfile( """ import time @@ -327,7 +314,7 @@ def test_issue185_time_breaks(testdir): ) -def test_importerror(testdir): +def test_importerror(testdir: Testdir) -> None: p = testdir.mkpydir("package") p.join("a.py").write( textwrap.dedent( @@ -349,51 +336,36 @@ def test_importerror(testdir): result = testdir.runpytest() result.stdout.fnmatch_lines( """ - *import error in package.a: No module named {0}doesnotexist{0}* - """.format( - "'" if sys.version_info > (3, 0) else "" - ) + *import error in package.a: No module named 'doesnotexist'* + """ ) -class SampleNew(object): +class Sample: @staticmethod - def hello(): + def hello() -> bool: return True -class SampleNewInherit(SampleNew): - pass - - -class SampleOld(object): - # oldstyle on python2 - @staticmethod - def hello(): - return True - - -class SampleOldInherit(SampleOld): +class SampleInherit(Sample): pass @pytest.mark.parametrize( - "Sample", - [SampleNew, SampleNewInherit, SampleOld, SampleOldInherit], - ids=["new", "new-inherit", "old", "old-inherit"], + "Sample", [Sample, SampleInherit], ids=["new", "new-inherit"], ) -def test_issue156_undo_staticmethod(Sample): +def test_issue156_undo_staticmethod(Sample: "Type[Sample]") -> None: monkeypatch = MonkeyPatch() monkeypatch.setattr(Sample, "hello", None) assert Sample.hello is None - monkeypatch.undo() + monkeypatch.undo() # type: ignore[unreachable] assert Sample.hello() -def test_undo_class_descriptors_delattr(): - class SampleParent(object): +def test_undo_class_descriptors_delattr() -> None: + class SampleParent: @classmethod def hello(_cls): pass @@ -419,7 +391,7 @@ def test_undo_class_descriptors_delattr(): assert original_world == SampleChild.world -def test_issue1338_name_resolving(): +def test_issue1338_name_resolving() -> None: pytest.importorskip("requests") monkeypatch = MonkeyPatch() try: @@ -428,7 +400,7 @@ def test_issue1338_name_resolving(): monkeypatch.undo() -def test_context(): +def test_context() -> None: monkeypatch = MonkeyPatch() import functools @@ -440,7 +412,9 @@ def test_context(): assert inspect.isclass(functools.partial) -def test_syspath_prepend_with_namespace_packages(testdir, monkeypatch): +def test_syspath_prepend_with_namespace_packages( + testdir: Testdir, monkeypatch: MonkeyPatch +) -> None: for dirname in "hello", "world": d = testdir.mkdir(dirname) ns = d.mkdir("ns_pkg") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py index e86bd5f793e..f9026ec619f 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nodes.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- import py import pytest from _pytest import nodes +from _pytest.pytester import Testdir @pytest.mark.parametrize( @@ -18,12 +18,19 @@ from _pytest import nodes ("foo/bar", "foo/bar::TestBop", True), ), ) -def test_ischildnode(baseid, nodeid, expected): +def test_ischildnode(baseid: str, nodeid: str, expected: bool) -> None: result = nodes.ischildnode(baseid, nodeid) assert result is expected -def test_std_warn_not_pytestwarning(testdir): +def test_node_from_parent_disallowed_arguments() -> None: + with pytest.raises(TypeError, match="session is"): + nodes.Node.from_parent(None, session=None) # type: ignore[arg-type] + with pytest.raises(TypeError, match="config is"): + nodes.Node.from_parent(None, config=None) # type: ignore[arg-type] + + +def test_std_warn_not_pytestwarning(testdir: Testdir) -> None: items = testdir.getitems( """ def test(): @@ -31,24 +38,51 @@ def test_std_warn_not_pytestwarning(testdir): """ ) with pytest.raises(ValueError, match=".*instance of PytestWarning.*"): - items[0].warn(UserWarning("some warning")) + items[0].warn(UserWarning("some warning")) # type: ignore[arg-type] -def test__check_initialpaths_for_relpath(): +def test__check_initialpaths_for_relpath() -> None: """Ensure that it handles dirs, and does not always use dirname.""" cwd = py.path.local() - class FakeSession: + class FakeSession1: _initialpaths = [cwd] - assert nodes._check_initialpaths_for_relpath(FakeSession, cwd) == "" + assert nodes._check_initialpaths_for_relpath(FakeSession1, cwd) == "" sub = cwd.join("file") - class FakeSession: + class FakeSession2: _initialpaths = [cwd] - assert nodes._check_initialpaths_for_relpath(FakeSession, sub) == "file" + assert nodes._check_initialpaths_for_relpath(FakeSession2, sub) == "file" outside = py.path.local("/outside") - assert nodes._check_initialpaths_for_relpath(FakeSession, outside) is None + assert nodes._check_initialpaths_for_relpath(FakeSession2, outside) is None + + +def test_failure_with_changed_cwd(testdir): + """ + Test failure lines should use absolute paths if cwd has changed since + invocation, so the path is correct (#6428). + """ + p = testdir.makepyfile( + """ + import os + import pytest + + @pytest.fixture + def private_dir(): + out_dir = 'ddd' + os.mkdir(out_dir) + old_dir = os.getcwd() + os.chdir(out_dir) + yield out_dir + os.chdir(old_dir) + + def test_show_wrong_path(private_dir): + assert False + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines([str(p) + ":*: AssertionError", "*1 failed in *"]) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nose.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nose.py index 6eb5f085e84..b6200c6c9ad 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nose.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_nose.py @@ -1,8 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import pytest @@ -36,7 +31,7 @@ def test_setup_func_with_setup_decorator(): values = [] - class A(object): + class A: @pytest.fixture(autouse=True) def f(self): values.append(1) @@ -48,7 +43,7 @@ def test_setup_func_with_setup_decorator(): def test_setup_func_not_callable(): from _pytest.nose import call_optional - class A(object): + class A: f = 1 call_optional(A(), "f") @@ -234,7 +229,7 @@ def test_nose_setup_ordering(testdir): def test_apiwrapper_problem_issue260(testdir): # this would end up trying a call an optional teardown on the class - # for plain unittests we dont want nose behaviour + # for plain unittests we don't want nose behaviour testdir.makepyfile( """ import unittest @@ -258,7 +253,7 @@ def test_apiwrapper_problem_issue260(testdir): def test_setup_teardown_linking_issue265(testdir): - # we accidentally didnt integrate nose setupstate with normal setupstate + # we accidentally didn't integrate nose setupstate with normal setupstate # this test ensures that won't happen again testdir.makepyfile( ''' @@ -371,13 +366,59 @@ def test_nottest_class_decorator(testdir): def test_skip_test_with_unicode(testdir): testdir.makepyfile( - """ - # -*- coding: utf-8 -*- + """\ import unittest class TestClass(): def test_io(self): - raise unittest.SkipTest(u'😊') - """ + raise unittest.SkipTest('😊') + """ ) result = testdir.runpytest() result.stdout.fnmatch_lines(["* 1 skipped *"]) + + +def test_raises(testdir): + testdir.makepyfile( + """ + from nose.tools import raises + + @raises(RuntimeError) + def test_raises_runtimeerror(): + raise RuntimeError + + @raises(Exception) + def test_raises_baseexception_not_caught(): + raise BaseException + + @raises(BaseException) + def test_raises_baseexception_caught(): + raise BaseException + """ + ) + result = testdir.runpytest("-vv") + result.stdout.fnmatch_lines( + [ + "test_raises.py::test_raises_runtimeerror PASSED*", + "test_raises.py::test_raises_baseexception_not_caught FAILED*", + "test_raises.py::test_raises_baseexception_caught PASSED*", + "*= FAILURES =*", + "*_ test_raises_baseexception_not_caught _*", + "", + "arg = (), kw = {}", + "", + " def newfunc(*arg, **kw):", + " try:", + "> func(*arg, **kw)", + "", + "*/nose/*: ", + "_ _ *", + "", + " @raises(Exception)", + " def test_raises_baseexception_not_caught():", + "> raise BaseException", + "E BaseException", + "", + "test_raises.py:9: BaseException", + "* 1 failed, 2 passed *", + ] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py index c570ed60b2f..4d63d99eeab 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_parseopt.py @@ -1,11 +1,7 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import argparse -import distutils.spawn import os +import shlex +import subprocess import sys import py @@ -16,22 +12,22 @@ from _pytest.config.exceptions import UsageError @pytest.fixture -def parser(): +def parser() -> parseopt.Parser: return parseopt.Parser() -class TestParser(object): - def test_no_help_by_default(self): +class TestParser: + def test_no_help_by_default(self) -> None: parser = parseopt.Parser(usage="xyz") pytest.raises(UsageError, lambda: parser.parse(["-h"])) - def test_custom_prog(self, parser): + def test_custom_prog(self, parser: parseopt.Parser) -> None: """Custom prog can be set for `argparse.ArgumentParser`.""" assert parser._getparser().prog == os.path.basename(sys.argv[0]) parser.prog = "custom-prog" assert parser._getparser().prog == "custom-prog" - def test_argument(self): + def test_argument(self) -> None: with pytest.raises(parseopt.ArgumentError): # need a short or long option argument = parseopt.Argument() @@ -49,7 +45,7 @@ class TestParser(object): "Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')" ) - def test_argument_type(self): + def test_argument_type(self) -> None: argument = parseopt.Argument("-t", dest="abc", type=int) assert argument.type is int argument = parseopt.Argument("-t", dest="abc", type=str) @@ -64,7 +60,7 @@ class TestParser(object): ) assert argument.type is str - def test_argument_processopt(self): + def test_argument_processopt(self) -> None: argument = parseopt.Argument("-t", type=int) argument.default = 42 argument.dest = "abc" @@ -72,19 +68,19 @@ class TestParser(object): assert res["default"] == 42 assert res["dest"] == "abc" - def test_group_add_and_get(self, parser): + def test_group_add_and_get(self, parser: parseopt.Parser) -> None: group = parser.getgroup("hello", description="desc") assert group.name == "hello" assert group.description == "desc" - def test_getgroup_simple(self, parser): + def test_getgroup_simple(self, parser: parseopt.Parser) -> None: group = parser.getgroup("hello", description="desc") assert group.name == "hello" assert group.description == "desc" group2 = parser.getgroup("hello") assert group2 is group - def test_group_ordering(self, parser): + def test_group_ordering(self, parser: parseopt.Parser) -> None: parser.getgroup("1") parser.getgroup("2") parser.getgroup("3", after="1") @@ -92,20 +88,20 @@ class TestParser(object): groups_names = [x.name for x in groups] assert groups_names == list("132") - def test_group_addoption(self): + def test_group_addoption(self) -> None: group = parseopt.OptionGroup("hello") group.addoption("--option1", action="store_true") assert len(group.options) == 1 assert isinstance(group.options[0], parseopt.Argument) - def test_group_addoption_conflict(self): + def test_group_addoption_conflict(self) -> None: group = parseopt.OptionGroup("hello again") group.addoption("--option1", "--option-1", action="store_true") with pytest.raises(ValueError) as err: group.addoption("--option1", "--option-one", action="store_true") assert str({"--option1"}) in str(err.value) - def test_group_shortopt_lowercase(self, parser): + def test_group_shortopt_lowercase(self, parser: parseopt.Parser) -> None: group = parser.getgroup("hello") with pytest.raises(ValueError): group.addoption("-x", action="store_true") @@ -113,30 +109,30 @@ class TestParser(object): group._addoption("-x", action="store_true") assert len(group.options) == 1 - def test_parser_addoption(self, parser): + def test_parser_addoption(self, parser: parseopt.Parser) -> None: group = parser.getgroup("custom options") assert len(group.options) == 0 group.addoption("--option1", action="store_true") assert len(group.options) == 1 - def test_parse(self, parser): + def test_parse(self, parser: parseopt.Parser) -> None: parser.addoption("--hello", dest="hello", action="store") args = parser.parse(["--hello", "world"]) assert args.hello == "world" assert not getattr(args, parseopt.FILE_OR_DIR) - def test_parse2(self, parser): + def test_parse2(self, parser: parseopt.Parser) -> None: args = parser.parse([py.path.local()]) assert getattr(args, parseopt.FILE_OR_DIR)[0] == py.path.local() - def test_parse_known_args(self, parser): + def test_parse_known_args(self, parser: parseopt.Parser) -> None: parser.parse_known_args([py.path.local()]) parser.addoption("--hello", action="store_true") ns = parser.parse_known_args(["x", "--y", "--hello", "this"]) assert ns.hello assert ns.file_or_dir == ["x"] - def test_parse_known_and_unknown_args(self, parser): + def test_parse_known_and_unknown_args(self, parser: parseopt.Parser) -> None: parser.addoption("--hello", action="store_true") ns, unknown = parser.parse_known_and_unknown_args( ["x", "--y", "--hello", "this"] @@ -145,7 +141,7 @@ class TestParser(object): assert ns.file_or_dir == ["x"] assert unknown == ["--y", "this"] - def test_parse_will_set_default(self, parser): + def test_parse_will_set_default(self, parser: parseopt.Parser) -> None: parser.addoption("--hello", dest="hello", default="x", action="store") option = parser.parse([]) assert option.hello == "x" @@ -153,25 +149,22 @@ class TestParser(object): parser.parse_setoption([], option) assert option.hello == "x" - def test_parse_setoption(self, parser): + def test_parse_setoption(self, parser: parseopt.Parser) -> None: parser.addoption("--hello", dest="hello", action="store") parser.addoption("--world", dest="world", default=42) - class A(object): - pass - - option = A() + option = argparse.Namespace() args = parser.parse_setoption(["--hello", "world"], option) assert option.hello == "world" assert option.world == 42 assert not args - def test_parse_special_destination(self, parser): + def test_parse_special_destination(self, parser: parseopt.Parser) -> None: parser.addoption("--ultimate-answer", type=int) args = parser.parse(["--ultimate-answer", "42"]) assert args.ultimate_answer == 42 - def test_parse_split_positional_arguments(self, parser): + def test_parse_split_positional_arguments(self, parser: parseopt.Parser) -> None: parser.addoption("-R", action="store_true") parser.addoption("-S", action="store_false") args = parser.parse(["-R", "4", "2", "-S"]) @@ -185,7 +178,7 @@ class TestParser(object): assert args.R is True assert args.S is False - def test_parse_defaultgetter(self): + def test_parse_defaultgetter(self) -> None: def defaultget(option): if not hasattr(option, "type"): return @@ -203,17 +196,17 @@ class TestParser(object): assert option.this == 42 assert option.no is False - def test_drop_short_helper(self): + def test_drop_short_helper(self) -> None: parser = argparse.ArgumentParser( - formatter_class=parseopt.DropShorterLongHelpFormatter + formatter_class=parseopt.DropShorterLongHelpFormatter, allow_abbrev=False ) parser.add_argument( "-t", "--twoword", "--duo", "--two-word", "--two", help="foo" - ).map_long_option = {"two": "two-word"} + ) # throws error on --deux only! parser.add_argument( "-d", "--deuxmots", "--deux-mots", action="store_true", help="foo" - ).map_long_option = {"deux": "deux-mots"} + ) parser.add_argument("-s", action="store_true", help="single short") parser.add_argument("--abc", "-a", action="store_true", help="bar") parser.add_argument("--klm", "-k", "--kl-m", action="store_true", help="bar") @@ -225,7 +218,7 @@ class TestParser(object): ) parser.add_argument( "-x", "--exit-on-first", "--exitfirst", action="store_true", help="spam" - ).map_long_option = {"exitfirst": "exit-on-first"} + ) parser.add_argument("files_and_dirs", nargs="*") args = parser.parse_args(["-k", "--duo", "hallo", "--exitfirst"]) assert args.twoword == "hallo" @@ -240,34 +233,32 @@ class TestParser(object): args = parser.parse_args(["file", "dir"]) assert "|".join(args.files_and_dirs) == "file|dir" - def test_drop_short_0(self, parser): + def test_drop_short_0(self, parser: parseopt.Parser) -> None: parser.addoption("--funcarg", "--func-arg", action="store_true") parser.addoption("--abc-def", "--abc-def", action="store_true") parser.addoption("--klm-hij", action="store_true") - args = parser.parse(["--funcarg", "--k"]) - assert args.funcarg is True - assert args.abc_def is False - assert args.klm_hij is True + with pytest.raises(UsageError): + parser.parse(["--funcarg", "--k"]) - def test_drop_short_2(self, parser): + def test_drop_short_2(self, parser: parseopt.Parser) -> None: parser.addoption("--func-arg", "--doit", action="store_true") args = parser.parse(["--doit"]) assert args.func_arg is True - def test_drop_short_3(self, parser): + def test_drop_short_3(self, parser: parseopt.Parser) -> None: parser.addoption("--func-arg", "--funcarg", "--doit", action="store_true") args = parser.parse(["abcd"]) assert args.func_arg is False assert args.file_or_dir == ["abcd"] - def test_drop_short_help0(self, parser, capsys): + def test_drop_short_help0(self, parser: parseopt.Parser) -> None: parser.addoption("--func-args", "--doit", help="foo", action="store_true") parser.parse([]) help = parser.optparser.format_help() assert "--func-args, --doit foo" in help # testing would be more helpful with all help generated - def test_drop_short_help1(self, parser, capsys): + def test_drop_short_help1(self, parser: parseopt.Parser) -> None: group = parser.getgroup("general") group.addoption("--doit", "--func-args", action="store_true", help="foo") group._addoption( @@ -281,7 +272,7 @@ class TestParser(object): help = parser.optparser.format_help() assert "-doit, --func-args foo" in help - def test_multiple_metavar_help(self, parser): + def test_multiple_metavar_help(self, parser: parseopt.Parser) -> None: """ Help text for options with a metavar tuple should display help in the form "--preferences=value1 value2 value3" (#2004). @@ -296,17 +287,33 @@ class TestParser(object): assert "--preferences=value1 value2 value3" in help -def test_argcomplete(testdir, monkeypatch): - if not distutils.spawn.find_executable("bash"): - pytest.skip("bash not available") +def test_argcomplete(testdir, monkeypatch) -> None: + try: + bash_version = subprocess.run( + ["bash", "--version"], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + check=True, + universal_newlines=True, + ).stdout + except (OSError, subprocess.CalledProcessError): + pytest.skip("bash is not available") + if "GNU bash" not in bash_version: + # See #7518. + pytest.skip("not a real bash") + script = str(testdir.tmpdir.join("test_argcomplete")) with open(str(script), "w") as fp: # redirect output from argcomplete to stdin and stderr is not trivial # http://stackoverflow.com/q/12589419/1307905 # so we use bash - fp.write('COMP_WORDBREAKS="$COMP_WORDBREAKS" python -m pytest 8>&1 9>&2') - # alternative would be exteneded Testdir.{run(),_run(),popen()} to be able + fp.write( + 'COMP_WORDBREAKS="$COMP_WORDBREAKS" {} -m pytest 8>&1 9>&2'.format( + shlex.quote(sys.executable) + ) + ) + # alternative would be extended Testdir.{run(),_run(),popen()} to be able # to handle a keyword argument env that replaces os.environ in popen or # extends the copy, advantage: could not forget to restore monkeypatch.setenv("_ARGCOMPLETE", "1") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py index 02efdb55407..7f88a13eb43 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pastebin.py @@ -1,17 +1,13 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import sys +from typing import List +from typing import Union import pytest -class TestPasteCapture(object): +class TestPasteCapture: @pytest.fixture - def pastebinlist(self, monkeypatch, request): - pastebinlist = [] + def pastebinlist(self, monkeypatch, request) -> List[Union[str, bytes]]: + pastebinlist = [] # type: List[Union[str, bytes]] plugin = request.config.pluginmanager.getplugin("pastebin") monkeypatch.setattr(plugin, "create_new_paste", pastebinlist.append) return pastebinlist @@ -28,7 +24,7 @@ class TestPasteCapture(object): pytest.skip("") """ ) - reprec = testdir.inline_run(testpath, "--paste=failed") + reprec = testdir.inline_run(testpath, "--pastebin=failed") assert len(pastebinlist) == 1 s = pastebinlist[0] assert s.find("def test_fail") != -1 @@ -62,22 +58,18 @@ class TestPasteCapture(object): ] ) - def test_non_ascii_paste_text(self, testdir): + def test_non_ascii_paste_text(self, testdir, pastebinlist): """Make sure that text which contains non-ascii characters is pasted correctly. See #1219. """ testdir.makepyfile( - test_unicode=""" - # -*- coding: utf-8 -*- + test_unicode="""\ def test(): assert '☺' == 1 - """ + """ ) result = testdir.runpytest("--pastebin=all") - if sys.version_info[0] >= 3: - expected_msg = "*assert '☺' == 1*" - else: - expected_msg = "*assert '\\xe2\\x98\\xba' == 1*" + expected_msg = "*assert '☺' == 1*" result.stdout.fnmatch_lines( [ expected_msg, @@ -85,41 +77,85 @@ class TestPasteCapture(object): "*Sending information to Paste Service*", ] ) + assert len(pastebinlist) == 1 -class TestPaste(object): +class TestPaste: @pytest.fixture def pastebin(self, request): return request.config.pluginmanager.getplugin("pastebin") @pytest.fixture + def mocked_urlopen_fail(self, monkeypatch): + """Monkeypatch the actual urlopen call to emulate a HTTP Error 400.""" + calls = [] + + import urllib.error + import urllib.request + + def mocked(url, data): + calls.append((url, data)) + raise urllib.error.HTTPError(url, 400, "Bad request", None, None) + + monkeypatch.setattr(urllib.request, "urlopen", mocked) + return calls + + @pytest.fixture + def mocked_urlopen_invalid(self, monkeypatch): + """Monkeypatch the actual urlopen calls done by the internal plugin + function that connects to bpaste service, but return a url in an + unexpected format.""" + calls = [] + + def mocked(url, data): + calls.append((url, data)) + + class DummyFile: + def read(self): + # part of html of a normal response + return b'View <a href="/invalid/3c0c6750bd">raw</a>.' + + return DummyFile() + + import urllib.request + + monkeypatch.setattr(urllib.request, "urlopen", mocked) + return calls + + @pytest.fixture def mocked_urlopen(self, monkeypatch): - """ - monkeypatch the actual urlopen calls done by the internal plugin - function that connects to bpaste service. - """ + """Monkeypatch the actual urlopen calls done by the internal plugin + function that connects to bpaste service.""" calls = [] def mocked(url, data): calls.append((url, data)) - class DummyFile(object): + class DummyFile: def read(self): # part of html of a normal response return b'View <a href="/raw/3c0c6750bd">raw</a>.' return DummyFile() - if sys.version_info < (3, 0): - import urllib - - monkeypatch.setattr(urllib, "urlopen", mocked) - else: - import urllib.request + import urllib.request - monkeypatch.setattr(urllib.request, "urlopen", mocked) + monkeypatch.setattr(urllib.request, "urlopen", mocked) return calls + def test_pastebin_invalid_url(self, pastebin, mocked_urlopen_invalid): + result = pastebin.create_new_paste(b"full-paste-contents") + assert ( + result + == "bad response: invalid format ('View <a href=\"/invalid/3c0c6750bd\">raw</a>.')" + ) + assert len(mocked_urlopen_invalid) == 1 + + def test_pastebin_http_error(self, pastebin, mocked_urlopen_fail): + result = pastebin.create_new_paste(b"full-paste-contents") + assert result == "bad response: HTTP Error 400: Bad request" + assert len(mocked_urlopen_fail) == 1 + def test_create_new_paste(self, pastebin, mocked_urlopen): result = pastebin.create_new_paste(b"full-paste-contents") assert result == "https://bpaste.net/show/3c0c6750bd" @@ -131,3 +167,15 @@ class TestPaste(object): assert "lexer=%s" % lexer in data.decode() assert "code=full-paste-contents" in data.decode() assert "expiry=1week" in data.decode() + + def test_create_new_paste_failure(self, pastebin, monkeypatch): + import io + import urllib.request + + def response(url, data): + stream = io.BytesIO(b"something bad occurred") + return stream + + monkeypatch.setattr(urllib.request, "urlopen", response) + result = pastebin.create_new_paste(b"full-paste-contents") + assert result == "bad response: invalid format ('something bad occurred')" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py index 541d289f777..41228d6b095 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pathlib.py @@ -1,19 +1,27 @@ -# -*- coding: utf-8 -*- +import os.path import sys +import unittest.mock +from textwrap import dedent import py import pytest +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import commonpath +from _pytest.pathlib import ensure_deletable from _pytest.pathlib import fnmatch_ex +from _pytest.pathlib import get_extended_length_path_str from _pytest.pathlib import get_lock_path +from _pytest.pathlib import import_path +from _pytest.pathlib import ImportPathMismatchError from _pytest.pathlib import maybe_delete_a_numbered_dir from _pytest.pathlib import Path +from _pytest.pathlib import resolve_package_path -class TestPort: - """Test that our port of py.common.FNMatcher (fnmatch_ex) produces the same results as the - original py.path.local.fnmatch method. - """ +class TestFNMatcherPort: + """Test that our port of py.common.FNMatcher (fnmatch_ex) produces the + same results as the original py.path.local.fnmatch method.""" @pytest.fixture(params=["pathlib", "py.path"]) def match(self, request): @@ -54,6 +62,10 @@ class TestPort: def test_matching(self, match, pattern, path): assert match(pattern, path) + def test_matching_abspath(self, match): + abspath = os.path.abspath(os.path.join("tests/foo.py")) + assert match("tests/foo.py", abspath) + @pytest.mark.parametrize( "pattern, path", [ @@ -72,6 +84,242 @@ class TestPort: assert not match(pattern, path) +class TestImportPath: + """ + + Most of the tests here were copied from py lib's tests for "py.local.path.pyimport". + + Having our own pyimport-like function is inline with removing py.path dependency in the future. + """ + + @pytest.fixture(scope="session") + def path1(self, tmpdir_factory): + path = tmpdir_factory.mktemp("path") + self.setuptestfs(path) + yield path + assert path.join("samplefile").check() + + def setuptestfs(self, path): + # print "setting up test fs for", repr(path) + samplefile = path.ensure("samplefile") + samplefile.write("samplefile\n") + + execfile = path.ensure("execfile") + execfile.write("x=42") + + execfilepy = path.ensure("execfile.py") + execfilepy.write("x=42") + + d = {1: 2, "hello": "world", "answer": 42} + path.ensure("samplepickle").dump(d) + + sampledir = path.ensure("sampledir", dir=1) + sampledir.ensure("otherfile") + + otherdir = path.ensure("otherdir", dir=1) + otherdir.ensure("__init__.py") + + module_a = otherdir.ensure("a.py") + module_a.write("from .b import stuff as result\n") + module_b = otherdir.ensure("b.py") + module_b.write('stuff="got it"\n') + module_c = otherdir.ensure("c.py") + module_c.write( + dedent( + """ + import py; + import otherdir.a + value = otherdir.a.result + """ + ) + ) + module_d = otherdir.ensure("d.py") + module_d.write( + dedent( + """ + import py; + from otherdir import a + value2 = a.result + """ + ) + ) + + def test_smoke_test(self, path1): + obj = import_path(path1.join("execfile.py")) + assert obj.x == 42 # type: ignore[attr-defined] + assert obj.__name__ == "execfile" + + def test_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch): + p = tmpdir.ensure("a", "test_x123.py") + import_path(p) + tmpdir.join("a").move(tmpdir.join("b")) + with pytest.raises(ImportPathMismatchError): + import_path(tmpdir.join("b", "test_x123.py")) + + # Errors can be ignored. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") + import_path(tmpdir.join("b", "test_x123.py")) + + # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") + with pytest.raises(ImportPathMismatchError): + import_path(tmpdir.join("b", "test_x123.py")) + + def test_messy_name(self, tmpdir): + # http://bitbucket.org/hpk42/py-trunk/issue/129 + path = tmpdir.ensure("foo__init__.py") + module = import_path(path) + assert module.__name__ == "foo__init__" + + def test_dir(self, tmpdir): + p = tmpdir.join("hello_123") + p_init = p.ensure("__init__.py") + m = import_path(p) + assert m.__name__ == "hello_123" + m = import_path(p_init) + assert m.__name__ == "hello_123" + + def test_a(self, path1): + otherdir = path1.join("otherdir") + mod = import_path(otherdir.join("a.py")) + assert mod.result == "got it" # type: ignore[attr-defined] + assert mod.__name__ == "otherdir.a" + + def test_b(self, path1): + otherdir = path1.join("otherdir") + mod = import_path(otherdir.join("b.py")) + assert mod.stuff == "got it" # type: ignore[attr-defined] + assert mod.__name__ == "otherdir.b" + + def test_c(self, path1): + otherdir = path1.join("otherdir") + mod = import_path(otherdir.join("c.py")) + assert mod.value == "got it" # type: ignore[attr-defined] + + def test_d(self, path1): + otherdir = path1.join("otherdir") + mod = import_path(otherdir.join("d.py")) + assert mod.value2 == "got it" # type: ignore[attr-defined] + + def test_import_after(self, tmpdir): + tmpdir.ensure("xxxpackage", "__init__.py") + mod1path = tmpdir.ensure("xxxpackage", "module1.py") + mod1 = import_path(mod1path) + assert mod1.__name__ == "xxxpackage.module1" + from xxxpackage import module1 + + assert module1 is mod1 + + def test_check_filepath_consistency(self, monkeypatch, tmpdir): + name = "pointsback123" + ModuleType = type(os) + p = tmpdir.ensure(name + ".py") + for ending in (".pyc", ".pyo"): + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + ending) + mod.__file__ = str(pseudopath) + monkeypatch.setitem(sys.modules, name, mod) + newmod = import_path(p) + assert mod == newmod + monkeypatch.undo() + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + "123.py") + mod.__file__ = str(pseudopath) + monkeypatch.setitem(sys.modules, name, mod) + with pytest.raises(ImportPathMismatchError) as excinfo: + import_path(p) + modname, modfile, orig = excinfo.value.args + assert modname == name + assert modfile == pseudopath + assert orig == p + assert issubclass(ImportPathMismatchError, ImportError) + + def test_issue131_on__init__(self, tmpdir): + # __init__.py files may be namespace packages, and thus the + # __file__ of an imported module may not be ourselves + # see issue + p1 = tmpdir.ensure("proja", "__init__.py") + p2 = tmpdir.ensure("sub", "proja", "__init__.py") + m1 = import_path(p1) + m2 = import_path(p2) + assert m1 == m2 + + def test_ensuresyspath_append(self, tmpdir): + root1 = tmpdir.mkdir("root1") + file1 = root1.ensure("x123.py") + assert str(root1) not in sys.path + import_path(file1, mode="append") + assert str(root1) == sys.path[-1] + assert str(root1) not in sys.path[:-1] + + def test_invalid_path(self, tmpdir): + with pytest.raises(ImportError): + import_path(tmpdir.join("invalid.py")) + + @pytest.fixture + def simple_module(self, tmpdir): + fn = tmpdir.join("mymod.py") + fn.write( + dedent( + """ + def foo(x): return 40 + x + """ + ) + ) + return fn + + def test_importmode_importlib(self, simple_module): + """`importlib` mode does not change sys.path.""" + module = import_path(simple_module, mode="importlib") + assert module.foo(2) == 42 # type: ignore[attr-defined] + assert simple_module.dirname not in sys.path + + def test_importmode_twice_is_different_module(self, simple_module): + """`importlib` mode always returns a new module.""" + module1 = import_path(simple_module, mode="importlib") + module2 = import_path(simple_module, mode="importlib") + assert module1 is not module2 + + def test_no_meta_path_found(self, simple_module, monkeypatch): + """Even without any meta_path should still import module.""" + monkeypatch.setattr(sys, "meta_path", []) + module = import_path(simple_module, mode="importlib") + assert module.foo(2) == 42 # type: ignore[attr-defined] + + # mode='importlib' fails if no spec is found to load the module + import importlib.util + + monkeypatch.setattr( + importlib.util, "spec_from_file_location", lambda *args: None + ) + with pytest.raises(ImportError): + import_path(simple_module, mode="importlib") + + +def test_resolve_package_path(tmp_path): + pkg = tmp_path / "pkg1" + pkg.mkdir() + (pkg / "__init__.py").touch() + (pkg / "subdir").mkdir() + (pkg / "subdir/__init__.py").touch() + assert resolve_package_path(pkg) == pkg + assert resolve_package_path(pkg.joinpath("subdir", "__init__.py")) == pkg + + +def test_package_unimportable(tmp_path): + pkg = tmp_path / "pkg1-1" + pkg.mkdir() + pkg.joinpath("__init__.py").touch() + subdir = pkg.joinpath("subdir") + subdir.mkdir() + pkg.joinpath("subdir/__init__.py").touch() + assert resolve_package_path(subdir) == subdir + xyz = subdir.joinpath("xyz.py") + xyz.touch() + assert resolve_package_path(xyz) == subdir + assert not resolve_package_path(pkg) + + def test_access_denied_during_cleanup(tmp_path, monkeypatch): """Ensure that deleting a numbered dir does not fail because of OSErrors (#4262).""" path = tmp_path / "temp-1" @@ -85,3 +333,71 @@ def test_access_denied_during_cleanup(tmp_path, monkeypatch): lock_path = get_lock_path(path) maybe_delete_a_numbered_dir(path) assert not lock_path.is_file() + + +def test_long_path_during_cleanup(tmp_path): + """Ensure that deleting long path works (particularly on Windows (#6775)).""" + path = (tmp_path / ("a" * 250)).resolve() + if sys.platform == "win32": + # make sure that the full path is > 260 characters without any + # component being over 260 characters + assert len(str(path)) > 260 + extended_path = "\\\\?\\" + str(path) + else: + extended_path = str(path) + os.mkdir(extended_path) + assert os.path.isdir(extended_path) + maybe_delete_a_numbered_dir(path) + assert not os.path.isdir(extended_path) + + +def test_get_extended_length_path_str(): + assert get_extended_length_path_str(r"c:\foo") == r"\\?\c:\foo" + assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo" + assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo" + assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo" + + +def test_suppress_error_removing_lock(tmp_path): + """ensure_deletable should be resilient if lock file cannot be removed (#5456, #7491)""" + path = tmp_path / "dir" + path.mkdir() + lock = get_lock_path(path) + lock.touch() + mtime = lock.stat().st_mtime + + with unittest.mock.patch.object(Path, "unlink", side_effect=OSError) as m: + assert not ensure_deletable( + path, consider_lock_dead_if_created_before=mtime + 30 + ) + assert m.call_count == 1 + assert lock.is_file() + + with unittest.mock.patch.object(Path, "is_file", side_effect=OSError) as m: + assert not ensure_deletable( + path, consider_lock_dead_if_created_before=mtime + 30 + ) + assert m.call_count == 1 + assert lock.is_file() + + # check now that we can remove the lock file in normal circumstances + assert ensure_deletable(path, consider_lock_dead_if_created_before=mtime + 30) + assert not lock.is_file() + + +def test_bestrelpath() -> None: + curdir = Path("/foo/bar/baz/path") + assert bestrelpath(curdir, curdir) == "." + assert bestrelpath(curdir, curdir / "hello" / "world") == "hello" + os.sep + "world" + assert bestrelpath(curdir, curdir.parent / "sister") == ".." + os.sep + "sister" + assert bestrelpath(curdir, curdir.parent) == ".." + assert bestrelpath(curdir, Path("hello")) == "hello" + + +def test_commonpath() -> None: + path = Path("/foo/bar/baz/path") + subpath = path / "sampledir" + assert commonpath(path, subpath) == path + assert commonpath(subpath, path) == path + assert commonpath(Path(str(path) + "suffix"), path) == path.parent + assert commonpath(path, path.parent.parent) == path.parent.parent diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pdb.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pdb.py deleted file mode 100644 index d31d60469cd..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pdb.py +++ /dev/null @@ -1,1237 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import sys - -import six - -import _pytest._code -import pytest -from _pytest.debugging import _validate_usepdb_cls - -try: - breakpoint -except NameError: - SUPPORTS_BREAKPOINT_BUILTIN = False -else: - SUPPORTS_BREAKPOINT_BUILTIN = True - - -_ENVIRON_PYTHONBREAKPOINT = os.environ.get("PYTHONBREAKPOINT", "") - - -def runpdb_and_get_report(testdir, source): - p = testdir.makepyfile(source) - result = testdir.runpytest_inprocess("--pdb", p) - reports = result.reprec.getreports("pytest_runtest_logreport") - assert len(reports) == 3, reports # setup/call/teardown - return reports[1] - - -@pytest.fixture -def custom_pdb_calls(): - called = [] - - # install dummy debugger class and track which methods were called on it - class _CustomPdb(object): - quitting = False - - def __init__(self, *args, **kwargs): - called.append("init") - - def reset(self): - called.append("reset") - - def interaction(self, *args): - called.append("interaction") - - _pytest._CustomPdb = _CustomPdb - return called - - -@pytest.fixture -def custom_debugger_hook(): - called = [] - - # install dummy debugger class and track which methods were called on it - class _CustomDebugger(object): - def __init__(self, *args, **kwargs): - called.append("init") - - def reset(self): - called.append("reset") - - def interaction(self, *args): - called.append("interaction") - - def set_trace(self, frame): - print("**CustomDebugger**") - called.append("set_trace") - - _pytest._CustomDebugger = _CustomDebugger - yield called - del _pytest._CustomDebugger - - -class TestPDB(object): - @pytest.fixture - def pdblist(self, request): - monkeypatch = request.getfixturevalue("monkeypatch") - pdblist = [] - - def mypdb(*args): - pdblist.append(args) - - plugin = request.config.pluginmanager.getplugin("debugging") - monkeypatch.setattr(plugin, "post_mortem", mypdb) - return pdblist - - def test_pdb_on_fail(self, testdir, pdblist): - rep = runpdb_and_get_report( - testdir, - """ - def test_func(): - assert 0 - """, - ) - assert rep.failed - assert len(pdblist) == 1 - tb = _pytest._code.Traceback(pdblist[0][0]) - assert tb[-1].name == "test_func" - - def test_pdb_on_xfail(self, testdir, pdblist): - rep = runpdb_and_get_report( - testdir, - """ - import pytest - @pytest.mark.xfail - def test_func(): - assert 0 - """, - ) - assert "xfail" in rep.keywords - assert not pdblist - - def test_pdb_on_skip(self, testdir, pdblist): - rep = runpdb_and_get_report( - testdir, - """ - import pytest - def test_func(): - pytest.skip("hello") - """, - ) - assert rep.skipped - assert len(pdblist) == 0 - - def test_pdb_on_BdbQuit(self, testdir, pdblist): - rep = runpdb_and_get_report( - testdir, - """ - import bdb - def test_func(): - raise bdb.BdbQuit - """, - ) - assert rep.failed - assert len(pdblist) == 0 - - def test_pdb_on_KeyboardInterrupt(self, testdir, pdblist): - rep = runpdb_and_get_report( - testdir, - """ - def test_func(): - raise KeyboardInterrupt - """, - ) - assert rep.failed - assert len(pdblist) == 1 - - @staticmethod - def flush(child): - if child.isalive(): - # Read if the test has not (e.g. test_pdb_unittest_skip). - child.read() - child.wait() - assert not child.isalive() - - def test_pdb_unittest_postmortem(self, testdir): - p1 = testdir.makepyfile( - """ - import unittest - class Blub(unittest.TestCase): - def tearDown(self): - self.filename = None - def test_false(self): - self.filename = 'debug' + '.me' - assert 0 - """ - ) - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect("Pdb") - child.sendline("p self.filename") - child.sendeof() - rest = child.read().decode("utf8") - assert "debug.me" in rest - self.flush(child) - - def test_pdb_unittest_skip(self, testdir): - """Test for issue #2137""" - p1 = testdir.makepyfile( - """ - import unittest - @unittest.skipIf(True, 'Skipping also with pdb active') - class MyTestCase(unittest.TestCase): - def test_one(self): - assert 0 - """ - ) - child = testdir.spawn_pytest("-rs --pdb %s" % p1) - child.expect("Skipping also with pdb active") - child.expect("1 skipped in") - child.sendeof() - self.flush(child) - - def test_pdb_print_captured_stdout_and_stderr(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(): - import sys - sys.stderr.write("get\\x20rekt") - print("get\\x20rekt") - assert False - - def test_not_called_due_to_quit(): - pass - """ - ) - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect("captured stdout") - child.expect("get rekt") - child.expect("captured stderr") - child.expect("get rekt") - child.expect("traceback") - child.expect("def test_1") - child.expect("Pdb") - child.sendeof() - rest = child.read().decode("utf8") - assert "Exit: Quitting debugger" in rest - assert "= 1 failed in" in rest - assert "def test_1" not in rest - assert "get rekt" not in rest - self.flush(child) - - def test_pdb_dont_print_empty_captured_stdout_and_stderr(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(): - assert False - """ - ) - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect("Pdb") - output = child.before.decode("utf8") - child.sendeof() - assert "captured stdout" not in output - assert "captured stderr" not in output - self.flush(child) - - @pytest.mark.parametrize("showcapture", ["all", "no", "log"]) - def test_pdb_print_captured_logs(self, testdir, showcapture): - p1 = testdir.makepyfile( - """ - def test_1(): - import logging - logging.warn("get " + "rekt") - assert False - """ - ) - child = testdir.spawn_pytest( - "--show-capture={} --pdb {}".format(showcapture, p1) - ) - if showcapture in ("all", "log"): - child.expect("captured log") - child.expect("get rekt") - child.expect("Pdb") - child.sendeof() - rest = child.read().decode("utf8") - assert "1 failed" in rest - self.flush(child) - - def test_pdb_print_captured_logs_nologging(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(): - import logging - logging.warn("get " + "rekt") - assert False - """ - ) - child = testdir.spawn_pytest("--show-capture=all --pdb -p no:logging %s" % p1) - child.expect("get rekt") - output = child.before.decode("utf8") - assert "captured log" not in output - child.expect("Pdb") - child.sendeof() - rest = child.read().decode("utf8") - assert "1 failed" in rest - self.flush(child) - - def test_pdb_interaction_exception(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - def globalfunc(): - pass - def test_1(): - pytest.raises(ValueError, globalfunc) - """ - ) - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect(".*def test_1") - child.expect(".*pytest.raises.*globalfunc") - child.expect("Pdb") - child.sendline("globalfunc") - child.expect(".*function") - child.sendeof() - child.expect("1 failed") - self.flush(child) - - def test_pdb_interaction_on_collection_issue181(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - xxx - """ - ) - child = testdir.spawn_pytest("--pdb %s" % p1) - # child.expect(".*import pytest.*") - child.expect("Pdb") - child.sendline("c") - child.expect("1 error") - self.flush(child) - - def test_pdb_interaction_on_internal_error(self, testdir): - testdir.makeconftest( - """ - def pytest_runtest_protocol(): - 0/0 - """ - ) - p1 = testdir.makepyfile("def test_func(): pass") - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect("Pdb") - - # INTERNALERROR is only displayed once via terminal reporter. - assert ( - len( - [ - x - for x in child.before.decode().splitlines() - if x.startswith("INTERNALERROR> Traceback") - ] - ) - == 1 - ) - - child.sendeof() - self.flush(child) - - def test_pdb_interaction_capturing_simple(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - def test_1(): - i = 0 - print("hello17") - pytest.set_trace() - i == 1 - assert 0 - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect(r"test_1\(\)") - child.expect("i == 1") - child.expect("Pdb") - child.sendline("c") - rest = child.read().decode("utf-8") - assert "AssertionError" in rest - assert "1 failed" in rest - assert "def test_1" in rest - assert "hello17" in rest # out is captured - self.flush(child) - - def test_pdb_set_trace_kwargs(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - def test_1(): - i = 0 - print("hello17") - pytest.set_trace(header="== my_header ==") - x = 3 - assert 0 - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("== my_header ==") - assert "PDB set_trace" not in child.before.decode() - child.expect("Pdb") - child.sendline("c") - rest = child.read().decode("utf-8") - assert "1 failed" in rest - assert "def test_1" in rest - assert "hello17" in rest # out is captured - self.flush(child) - - def test_pdb_set_trace_interception(self, testdir): - p1 = testdir.makepyfile( - """ - import pdb - def test_1(): - pdb.set_trace() - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("test_1") - child.expect("Pdb") - child.sendline("q") - rest = child.read().decode("utf8") - assert "no tests ran" in rest - assert "reading from stdin while output" not in rest - assert "BdbQuit" not in rest - self.flush(child) - - def test_pdb_and_capsys(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - def test_1(capsys): - print("hello1") - pytest.set_trace() - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("test_1") - child.send("capsys.readouterr()\n") - child.expect("hello1") - child.sendeof() - child.read() - self.flush(child) - - def test_pdb_with_caplog_on_pdb_invocation(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(capsys, caplog): - import logging - logging.getLogger(__name__).warning("some_warning") - assert 0 - """ - ) - child = testdir.spawn_pytest("--pdb %s" % str(p1)) - child.send("caplog.record_tuples\n") - child.expect_exact( - "[('test_pdb_with_caplog_on_pdb_invocation', 30, 'some_warning')]" - ) - child.sendeof() - child.read() - self.flush(child) - - def test_set_trace_capturing_afterwards(self, testdir): - p1 = testdir.makepyfile( - """ - import pdb - def test_1(): - pdb.set_trace() - def test_2(): - print("hello") - assert 0 - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("test_1") - child.send("c\n") - child.expect("test_2") - child.expect("Captured") - child.expect("hello") - child.sendeof() - child.read() - self.flush(child) - - def test_pdb_interaction_doctest(self, testdir, monkeypatch): - p1 = testdir.makepyfile( - """ - import pytest - def function_1(): - ''' - >>> i = 0 - >>> assert i == 1 - ''' - """ - ) - child = testdir.spawn_pytest("--doctest-modules --pdb %s" % p1) - child.expect("Pdb") - - assert "UNEXPECTED EXCEPTION: AssertionError()" in child.before.decode("utf8") - - child.sendline("'i=%i.' % i") - child.expect("Pdb") - assert "\r\n'i=0.'\r\n" in child.before.decode("utf8") - - child.sendeof() - rest = child.read().decode("utf8") - assert "1 failed" in rest - self.flush(child) - - def test_pdb_interaction_capturing_twice(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - def test_1(): - i = 0 - print("hello17") - pytest.set_trace() - x = 3 - print("hello18") - pytest.set_trace() - x = 4 - assert 0 - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect(r"PDB set_trace \(IO-capturing turned off\)") - child.expect("test_1") - child.expect("x = 3") - child.expect("Pdb") - child.sendline("c") - child.expect(r"PDB continue \(IO-capturing resumed\)") - child.expect(r"PDB set_trace \(IO-capturing turned off\)") - child.expect("x = 4") - child.expect("Pdb") - child.sendline("c") - child.expect("_ test_1 _") - child.expect("def test_1") - rest = child.read().decode("utf8") - assert "Captured stdout call" in rest - assert "hello17" in rest # out is captured - assert "hello18" in rest # out is captured - assert "1 failed" in rest - self.flush(child) - - def test_pdb_with_injected_do_debug(self, testdir): - """Simulates pdbpp, which injects Pdb into do_debug, and uses - self.__class__ in do_continue. - """ - p1 = testdir.makepyfile( - mytest=""" - import pdb - import pytest - - count_continue = 0 - - class CustomPdb(pdb.Pdb, object): - def do_debug(self, arg): - import sys - import types - - if sys.version_info < (3, ): - do_debug_func = pdb.Pdb.do_debug.im_func - else: - do_debug_func = pdb.Pdb.do_debug - - newglobals = do_debug_func.__globals__.copy() - newglobals['Pdb'] = self.__class__ - orig_do_debug = types.FunctionType( - do_debug_func.__code__, newglobals, - do_debug_func.__name__, do_debug_func.__defaults__, - ) - return orig_do_debug(self, arg) - do_debug.__doc__ = pdb.Pdb.do_debug.__doc__ - - def do_continue(self, *args, **kwargs): - global count_continue - count_continue += 1 - return super(CustomPdb, self).do_continue(*args, **kwargs) - - def foo(): - print("print_from_foo") - - def test_1(): - i = 0 - print("hello17") - pytest.set_trace() - x = 3 - print("hello18") - - assert count_continue == 2, "unexpected_failure: %d != 2" % count_continue - pytest.fail("expected_failure") - """ - ) - child = testdir.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1)) - child.expect(r"PDB set_trace \(IO-capturing turned off\)") - child.expect(r"\n\(Pdb") - child.sendline("debug foo()") - child.expect("ENTERING RECURSIVE DEBUGGER") - child.expect(r"\n\(\(Pdb") - child.sendline("c") - child.expect("LEAVING RECURSIVE DEBUGGER") - assert b"PDB continue" not in child.before - # No extra newline. - assert child.before.endswith(b"c\r\nprint_from_foo\r\n") - - # set_debug should not raise outcomes.Exit, if used recrursively. - child.sendline("debug 42") - child.sendline("q") - child.expect("LEAVING RECURSIVE DEBUGGER") - assert b"ENTERING RECURSIVE DEBUGGER" in child.before - assert b"Quitting debugger" not in child.before - - child.sendline("c") - child.expect(r"PDB continue \(IO-capturing resumed\)") - rest = child.read().decode("utf8") - assert "hello17" in rest # out is captured - assert "hello18" in rest # out is captured - assert "1 failed" in rest - assert "Failed: expected_failure" in rest - assert "AssertionError: unexpected_failure" not in rest - self.flush(child) - - def test_pdb_without_capture(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - def test_1(): - pytest.set_trace() - """ - ) - child = testdir.spawn_pytest("-s %s" % p1) - child.expect(r">>> PDB set_trace >>>") - child.expect("Pdb") - child.sendline("c") - child.expect(r">>> PDB continue >>>") - child.expect("1 passed") - self.flush(child) - - @pytest.mark.parametrize("capture_arg", ("", "-s", "-p no:capture")) - def test_pdb_continue_with_recursive_debug(self, capture_arg, testdir): - """Full coverage for do_debug without capturing. - - This is very similar to test_pdb_interaction_continue_recursive in general, - but mocks out ``pdb.set_trace`` for providing more coverage. - """ - p1 = testdir.makepyfile( - """ - try: - input = raw_input - except NameError: - pass - - def set_trace(): - __import__('pdb').set_trace() - - def test_1(monkeypatch): - import _pytest.debugging - - class pytestPDBTest(_pytest.debugging.pytestPDB): - @classmethod - def set_trace(cls, *args, **kwargs): - # Init PytestPdbWrapper to handle capturing. - _pdb = cls._init_pdb("set_trace", *args, **kwargs) - - # Mock out pdb.Pdb.do_continue. - import pdb - pdb.Pdb.do_continue = lambda self, arg: None - - print("===" + " SET_TRACE ===") - assert input() == "debug set_trace()" - - # Simulate PytestPdbWrapper.do_debug - cls._recursive_debug += 1 - print("ENTERING RECURSIVE DEBUGGER") - print("===" + " SET_TRACE_2 ===") - - assert input() == "c" - _pdb.do_continue("") - print("===" + " SET_TRACE_3 ===") - - # Simulate PytestPdbWrapper.do_debug - print("LEAVING RECURSIVE DEBUGGER") - cls._recursive_debug -= 1 - - print("===" + " SET_TRACE_4 ===") - assert input() == "c" - _pdb.do_continue("") - - def do_continue(self, arg): - print("=== do_continue") - - monkeypatch.setattr(_pytest.debugging, "pytestPDB", pytestPDBTest) - - import pdb - monkeypatch.setattr(pdb, "set_trace", pytestPDBTest.set_trace) - - set_trace() - """ - ) - child = testdir.spawn_pytest("--tb=short %s %s" % (p1, capture_arg)) - child.expect("=== SET_TRACE ===") - before = child.before.decode("utf8") - if not capture_arg: - assert ">>> PDB set_trace (IO-capturing turned off) >>>" in before - else: - assert ">>> PDB set_trace >>>" in before - child.sendline("debug set_trace()") - child.expect("=== SET_TRACE_2 ===") - before = child.before.decode("utf8") - assert "\r\nENTERING RECURSIVE DEBUGGER\r\n" in before - child.sendline("c") - child.expect("=== SET_TRACE_3 ===") - - # No continue message with recursive debugging. - before = child.before.decode("utf8") - assert ">>> PDB continue " not in before - - child.sendline("c") - child.expect("=== SET_TRACE_4 ===") - before = child.before.decode("utf8") - assert "\r\nLEAVING RECURSIVE DEBUGGER\r\n" in before - child.sendline("c") - rest = child.read().decode("utf8") - if not capture_arg: - assert "> PDB continue (IO-capturing resumed) >" in rest - else: - assert "> PDB continue >" in rest - assert "1 passed in" in rest - - def test_pdb_used_outside_test(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - pytest.set_trace() - x = 5 - """ - ) - child = testdir.spawn("{} {}".format(sys.executable, p1)) - child.expect("x = 5") - child.expect("Pdb") - child.sendeof() - self.flush(child) - - def test_pdb_used_in_generate_tests(self, testdir): - p1 = testdir.makepyfile( - """ - import pytest - def pytest_generate_tests(metafunc): - pytest.set_trace() - x = 5 - def test_foo(a): - pass - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("x = 5") - child.expect("Pdb") - child.sendeof() - self.flush(child) - - def test_pdb_collection_failure_is_shown(self, testdir): - p1 = testdir.makepyfile("xxx") - result = testdir.runpytest_subprocess("--pdb", p1) - result.stdout.fnmatch_lines( - ["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF - ) - - @pytest.mark.parametrize("post_mortem", (False, True)) - def test_enter_leave_pdb_hooks_are_called(self, post_mortem, testdir): - testdir.makeconftest( - """ - mypdb = None - - def pytest_configure(config): - config.testing_verification = 'configured' - - def pytest_enter_pdb(config, pdb): - assert config.testing_verification == 'configured' - print('enter_pdb_hook') - - global mypdb - mypdb = pdb - mypdb.set_attribute = "bar" - - def pytest_leave_pdb(config, pdb): - assert config.testing_verification == 'configured' - print('leave_pdb_hook') - - global mypdb - assert mypdb is pdb - assert mypdb.set_attribute == "bar" - """ - ) - p1 = testdir.makepyfile( - """ - import pytest - - def test_set_trace(): - pytest.set_trace() - assert 0 - - def test_post_mortem(): - assert 0 - """ - ) - if post_mortem: - child = testdir.spawn_pytest(str(p1) + " --pdb -s -k test_post_mortem") - else: - child = testdir.spawn_pytest(str(p1) + " -k test_set_trace") - child.expect("enter_pdb_hook") - child.sendline("c") - if post_mortem: - child.expect(r"PDB continue") - else: - child.expect(r"PDB continue \(IO-capturing resumed\)") - child.expect("Captured stdout call") - rest = child.read().decode("utf8") - assert "leave_pdb_hook" in rest - assert "1 failed" in rest - self.flush(child) - - def test_pdb_custom_cls(self, testdir, custom_pdb_calls): - p1 = testdir.makepyfile("""xxx """) - result = testdir.runpytest_inprocess("--pdb", "--pdbcls=_pytest:_CustomPdb", p1) - result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"]) - assert custom_pdb_calls == ["init", "reset", "interaction"] - - def test_pdb_custom_cls_invalid(self, testdir): - result = testdir.runpytest_inprocess("--pdbcls=invalid") - result.stderr.fnmatch_lines( - [ - "*: error: argument --pdbcls: 'invalid' is not in the format 'modname:classname'" - ] - ) - - def test_pdb_validate_usepdb_cls(self, testdir): - assert _validate_usepdb_cls("os.path:dirname.__name__") == ( - "os.path", - "dirname.__name__", - ) - - assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist") - - def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls): - p1 = testdir.makepyfile("""xxx """) - result = testdir.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1) - result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"]) - assert custom_pdb_calls == [] - - def test_pdb_custom_cls_with_set_trace(self, testdir, monkeypatch): - testdir.makepyfile( - custom_pdb=""" - class CustomPdb(object): - def __init__(self, *args, **kwargs): - skip = kwargs.pop("skip") - assert skip == ["foo.*"] - print("__init__") - super(CustomPdb, self).__init__(*args, **kwargs) - - def set_trace(*args, **kwargs): - print('custom set_trace>') - """ - ) - p1 = testdir.makepyfile( - """ - import pytest - - def test_foo(): - pytest.set_trace(skip=['foo.*']) - """ - ) - monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir)) - child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1)) - - child.expect("__init__") - child.expect("custom set_trace>") - self.flush(child) - - -class TestDebuggingBreakpoints(object): - def test_supports_breakpoint_module_global(self): - """ - Test that supports breakpoint global marks on Python 3.7+ and not on - CPython 3.5, 2.7 - """ - if sys.version_info.major == 3 and sys.version_info.minor >= 7: - assert SUPPORTS_BREAKPOINT_BUILTIN is True - if sys.version_info.major == 3 and sys.version_info.minor == 5: - assert SUPPORTS_BREAKPOINT_BUILTIN is False - if sys.version_info.major == 2 and sys.version_info.minor == 7: - assert SUPPORTS_BREAKPOINT_BUILTIN is False - - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) - @pytest.mark.parametrize("arg", ["--pdb", ""]) - def test_sys_breakpointhook_configure_and_unconfigure(self, testdir, arg): - """ - Test that sys.breakpointhook is set to the custom Pdb class once configured, test that - hook is reset to system value once pytest has been unconfigured - """ - testdir.makeconftest( - """ - import sys - from pytest import hookimpl - from _pytest.debugging import pytestPDB - - def pytest_configure(config): - config._cleanup.append(check_restored) - - def check_restored(): - assert sys.breakpointhook == sys.__breakpointhook__ - - def test_check(): - assert sys.breakpointhook == pytestPDB.set_trace - """ - ) - testdir.makepyfile( - """ - def test_nothing(): pass - """ - ) - args = (arg,) if arg else () - result = testdir.runpytest_subprocess(*args) - result.stdout.fnmatch_lines(["*1 passed in *"]) - - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) - def test_pdb_custom_cls(self, testdir, custom_debugger_hook): - p1 = testdir.makepyfile( - """ - def test_nothing(): - breakpoint() - """ - ) - result = testdir.runpytest_inprocess( - "--pdb", "--pdbcls=_pytest:_CustomDebugger", p1 - ) - result.stdout.fnmatch_lines(["*CustomDebugger*", "*1 passed*"]) - assert custom_debugger_hook == ["init", "set_trace"] - - @pytest.mark.parametrize("arg", ["--pdb", ""]) - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) - def test_environ_custom_class(self, testdir, custom_debugger_hook, arg): - testdir.makeconftest( - """ - import os - import sys - - os.environ['PYTHONBREAKPOINT'] = '_pytest._CustomDebugger.set_trace' - - def pytest_configure(config): - config._cleanup.append(check_restored) - - def check_restored(): - assert sys.breakpointhook == sys.__breakpointhook__ - - def test_check(): - import _pytest - assert sys.breakpointhook is _pytest._CustomDebugger.set_trace - """ - ) - testdir.makepyfile( - """ - def test_nothing(): pass - """ - ) - args = (arg,) if arg else () - result = testdir.runpytest_subprocess(*args) - result.stdout.fnmatch_lines(["*1 passed in *"]) - - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) - @pytest.mark.skipif( - not _ENVIRON_PYTHONBREAKPOINT == "", - reason="Requires breakpoint() default value", - ) - def test_sys_breakpoint_interception(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(): - breakpoint() - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("test_1") - child.expect("Pdb") - child.sendline("quit") - rest = child.read().decode("utf8") - assert "Quitting debugger" in rest - assert "reading from stdin while output" not in rest - TestPDB.flush(child) - - @pytest.mark.skipif( - not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin" - ) - def test_pdb_not_altered(self, testdir): - p1 = testdir.makepyfile( - """ - import pdb - def test_1(): - pdb.set_trace() - assert 0 - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("test_1") - child.expect("Pdb") - child.sendline("c") - rest = child.read().decode("utf8") - assert "1 failed" in rest - assert "reading from stdin while output" not in rest - TestPDB.flush(child) - - -class TestTraceOption: - def test_trace_sets_breakpoint(self, testdir): - p1 = testdir.makepyfile( - """ - def test_1(): - assert True - - def test_2(): - pass - - def test_3(): - pass - """ - ) - child = testdir.spawn_pytest("--trace " + str(p1)) - child.expect("test_1") - child.expect("Pdb") - child.sendline("c") - child.expect("test_2") - child.expect("Pdb") - child.sendline("c") - child.expect("test_3") - child.expect("Pdb") - child.sendline("q") - child.expect_exact("Exit: Quitting debugger") - rest = child.read().decode("utf8") - assert "2 passed in" in rest - assert "reading from stdin while output" not in rest - # Only printed once - not on stderr. - assert "Exit: Quitting debugger" not in child.before.decode("utf8") - TestPDB.flush(child) - - -def test_trace_after_runpytest(testdir): - """Test that debugging's pytest_configure is re-entrant.""" - p1 = testdir.makepyfile( - """ - from _pytest.debugging import pytestPDB - - def test_outer(testdir): - assert len(pytestPDB._saved) == 1 - - testdir.makepyfile( - \""" - from _pytest.debugging import pytestPDB - - def test_inner(): - assert len(pytestPDB._saved) == 2 - print() - print("test_inner_" + "end") - \""" - ) - - result = testdir.runpytest("-s", "-k", "test_inner") - assert result.ret == 0 - - assert len(pytestPDB._saved) == 1 - """ - ) - result = testdir.runpytest_subprocess("-s", "-p", "pytester", str(p1)) - result.stdout.fnmatch_lines(["test_inner_end"]) - assert result.ret == 0 - - -def test_quit_with_swallowed_SystemExit(testdir): - """Test that debugging's pytest_configure is re-entrant.""" - p1 = testdir.makepyfile( - """ - def call_pdb_set_trace(): - __import__('pdb').set_trace() - - - def test_1(): - try: - call_pdb_set_trace() - except SystemExit: - pass - - - def test_2(): - pass - """ - ) - child = testdir.spawn_pytest(str(p1)) - child.expect("Pdb") - child.sendline("q") - child.expect_exact("Exit: Quitting debugger") - rest = child.read().decode("utf8") - assert "no tests ran" in rest - TestPDB.flush(child) - - -@pytest.mark.parametrize("fixture", ("capfd", "capsys")) -def test_pdb_suspends_fixture_capturing(testdir, fixture): - """Using "-s" with pytest should suspend/resume fixture capturing.""" - p1 = testdir.makepyfile( - """ - def test_inner({fixture}): - import sys - - print("out_inner_before") - sys.stderr.write("err_inner_before\\n") - - __import__("pdb").set_trace() - - print("out_inner_after") - sys.stderr.write("err_inner_after\\n") - - out, err = {fixture}.readouterr() - assert out =="out_inner_before\\nout_inner_after\\n" - assert err =="err_inner_before\\nerr_inner_after\\n" - """.format( - fixture=fixture - ) - ) - - child = testdir.spawn_pytest(str(p1) + " -s") - - child.expect("Pdb") - before = child.before.decode("utf8") - assert ( - "> PDB set_trace (IO-capturing turned off for fixture %s) >" % (fixture) - in before - ) - - # Test that capturing is really suspended. - child.sendline("p 40 + 2") - child.expect("Pdb") - assert "\r\n42\r\n" in child.before.decode("utf8") - - child.sendline("c") - rest = child.read().decode("utf8") - assert "out_inner" not in rest - assert "err_inner" not in rest - - TestPDB.flush(child) - assert child.exitstatus == 0 - assert "= 1 passed in " in rest - assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest - - -def test_pdbcls_via_local_module(testdir): - """It should be imported in pytest_configure or later only.""" - p1 = testdir.makepyfile( - """ - def test(): - print("before_set_trace") - __import__("pdb").set_trace() - """, - mypdb=""" - class Wrapped: - class MyPdb: - def set_trace(self, *args): - print("set_trace_called", args) - - def runcall(self, *args, **kwds): - print("runcall_called", args, kwds) - assert "func" in kwds - """, - ) - result = testdir.runpytest( - str(p1), "--pdbcls=really.invalid:Value", syspathinsert=True - ) - result.stdout.fnmatch_lines( - [ - "*= FAILURES =*", - "E * --pdbcls: could not import 'really.invalid:Value': No module named *really*", - ] - ) - assert result.ret == 1 - - result = testdir.runpytest( - str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True - ) - assert result.ret == 0 - result.stdout.fnmatch_lines(["*set_trace_called*", "* 1 passed in *"]) - - # Ensure that it also works with --trace. - result = testdir.runpytest( - str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", "--trace", syspathinsert=True - ) - assert result.ret == 0 - result.stdout.fnmatch_lines(["*runcall_called*", "* 1 passed in *"]) - - -def test_raises_bdbquit_with_eoferror(testdir): - """It is not guaranteed that DontReadFromInput's read is called.""" - if six.PY2: - builtin_module = "__builtin__" - input_func = "raw_input" - else: - builtin_module = "builtins" - input_func = "input" - p1 = testdir.makepyfile( - """ - def input_without_read(*args, **kwargs): - raise EOFError() - - def test(monkeypatch): - import {builtin_module} - monkeypatch.setattr({builtin_module}, {input_func!r}, input_without_read) - __import__('pdb').set_trace() - """.format( - builtin_module=builtin_module, input_func=input_func - ) - ) - result = testdir.runpytest(str(p1)) - result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"]) - assert result.ret == 1 - - -def test_pdb_wrapper_class_is_reused(testdir): - p1 = testdir.makepyfile( - """ - def test(): - __import__("pdb").set_trace() - __import__("pdb").set_trace() - - import mypdb - instances = mypdb.instances - assert len(instances) == 2 - assert instances[0].__class__ is instances[1].__class__ - """, - mypdb=""" - instances = [] - - class MyPdb: - def __init__(self, *args, **kwargs): - instances.append(self) - - def set_trace(self, *args): - print("set_trace_called", args) - """, - ) - result = testdir.runpytest(str(p1), "--pdbcls=mypdb:MyPdb", syspathinsert=True) - assert result.ret == 0 - result.stdout.fnmatch_lines( - ["*set_trace_called*", "*set_trace_called*", "* 1 passed in *"] - ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py index 23655164343..a083f4b4f37 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pluginmanager.py @@ -1,25 +1,21 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import sys import types +from typing import List import pytest +from _pytest.config import ExitCode from _pytest.config import PytestPluginManager from _pytest.config.exceptions import UsageError -from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import Session @pytest.fixture -def pytestpm(): +def pytestpm() -> PytestPluginManager: return PytestPluginManager() -class TestPytestPluginInteractions(object): +class TestPytestPluginInteractions: def test_addhooks_conftestplugin(self, testdir, _config_for_test): testdir.makepyfile( newhooks=""" @@ -41,7 +37,7 @@ class TestPytestPluginInteractions(object): pm.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=config.pluginmanager) ) - config.pluginmanager._importconftest(conf) + config.pluginmanager._importconftest(conf, importmode="prepend") # print(config.pluginmanager.get_plugins()) res = config.hook.pytest_myhook(xyz=10) assert res == [11] @@ -68,15 +64,15 @@ class TestPytestPluginInteractions(object): default=True) """ ) - config.pluginmanager._importconftest(p) + config.pluginmanager._importconftest(p, importmode="prepend") assert config.option.test123 def test_configure(self, testdir): config = testdir.parseconfig() values = [] - class A(object): - def pytest_configure(self, config): + class A: + def pytest_configure(self): values.append(self) config.pluginmanager.register(A()) @@ -91,20 +87,20 @@ class TestPytestPluginInteractions(object): config.pluginmanager.register(A()) assert len(values) == 2 - def test_hook_tracing(self, _config_for_test): + def test_hook_tracing(self, _config_for_test) -> None: pytestpm = _config_for_test.pluginmanager # fully initialized with plugins saveindent = [] - class api1(object): + class api1: def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) - class api2(object): + class api2: def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) raise ValueError() - values = [] + values = [] # type: List[str] pytestpm.trace.root.setwriter(values.append) undo = pytestpm.enable_tracing() try: @@ -127,19 +123,49 @@ class TestPytestPluginInteractions(object): def test_hook_proxy(self, testdir): """Test the gethookproxy function(#2016)""" config = testdir.parseconfig() - session = Session(config) + session = Session.from_config(config) testdir.makepyfile(**{"tests/conftest.py": "", "tests/subdir/conftest.py": ""}) conftest1 = testdir.tmpdir.join("tests/conftest.py") conftest2 = testdir.tmpdir.join("tests/subdir/conftest.py") - config.pluginmanager._importconftest(conftest1) + config.pluginmanager._importconftest(conftest1, importmode="prepend") ihook_a = session.gethookproxy(testdir.tmpdir.join("tests")) assert ihook_a is not None - config.pluginmanager._importconftest(conftest2) + config.pluginmanager._importconftest(conftest2, importmode="prepend") ihook_b = session.gethookproxy(testdir.tmpdir.join("tests")) assert ihook_a is not ihook_b + def test_hook_with_addoption(self, testdir): + """Test that hooks can be used in a call to pytest_addoption""" + testdir.makepyfile( + newhooks=""" + import pytest + @pytest.hookspec(firstresult=True) + def pytest_default_value(): + pass + """ + ) + testdir.makepyfile( + myplugin=""" + import newhooks + def pytest_addhooks(pluginmanager): + pluginmanager.add_hookspecs(newhooks) + def pytest_addoption(parser, pluginmanager): + default_value = pluginmanager.hook.pytest_default_value() + parser.addoption("--config", help="Config, defaults to %(default)s", default=default_value) + """ + ) + testdir.makeconftest( + """ + pytest_plugins=("myplugin",) + def pytest_default_value(): + return "default_value" + """ + ) + res = testdir.runpytest("--help") + res.stdout.fnmatch_lines(["*--config=CONFIG*default_value*"]) + def test_default_markers(testdir): result = testdir.runpytest("--markers") @@ -154,12 +180,11 @@ def test_importplugin_error_message(testdir, pytestpm): """ testdir.syspathinsert(testdir.tmpdir) testdir.makepyfile( - qwe=""" - # -*- coding: utf-8 -*- + qwe="""\ def test_traceback(): - raise ImportError(u'Not possible to import: ☺') + raise ImportError('Not possible to import: ☺') test_traceback() - """ + """ ) with pytest.raises(ImportError) as excinfo: pytestpm.import_plugin("qwe") @@ -170,7 +195,7 @@ def test_importplugin_error_message(testdir, pytestpm): assert "in test_traceback" in str(excinfo.traceback[-1]) -class TestPytestPluginManager(object): +class TestPytestPluginManager: def test_register_imported_modules(self): pm = PytestPluginManager() mod = types.ModuleType("x.y.pytest_hello") @@ -191,20 +216,20 @@ class TestPytestPluginManager(object): assert pm.get_plugin("pytest_xyz") == mod assert pm.is_registered(mod) - def test_consider_module(self, testdir, pytestpm): + def test_consider_module(self, testdir, pytestpm: PytestPluginManager) -> None: testdir.syspathinsert() testdir.makepyfile(pytest_p1="#") testdir.makepyfile(pytest_p2="#") mod = types.ModuleType("temp") - mod.pytest_plugins = ["pytest_p1", "pytest_p2"] + mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"] pytestpm.consider_module(mod) assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1" assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2" - def test_consider_module_import_module(self, testdir, _config_for_test): + def test_consider_module_import_module(self, testdir, _config_for_test) -> None: pytestpm = _config_for_test.pluginmanager mod = types.ModuleType("x") - mod.pytest_plugins = "pytest_a" + mod.__dict__["pytest_plugins"] = "pytest_a" aplugin = testdir.makepyfile(pytest_a="#") reprec = testdir.make_hook_recorder(pytestpm) testdir.syspathinsert(aplugin.dirpath()) @@ -232,8 +257,8 @@ class TestPytestPluginManager(object): ) p.copy(p.dirpath("skipping2.py")) monkeypatch.setenv("PYTEST_PLUGINS", "skipping2") - result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True) - assert result.ret == EXIT_NOTESTSCOLLECTED + result = testdir.runpytest("-p", "skipping1", syspathinsert=True) + assert result.ret == ExitCode.NO_TESTS_COLLECTED result.stdout.fnmatch_lines( ["*skipped plugin*skipping1*hello*", "*skipped plugin*skipping2*hello*"] ) @@ -300,7 +325,7 @@ class TestPytestPluginManager(object): pytestpm.consider_conftest(mod) -class TestPytestPluginManagerBootstrapming(object): +class TestPytestPluginManagerBootstrapming: def test_preparse_args(self, pytestpm): pytest.raises( ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"]) @@ -337,10 +362,10 @@ class TestPytestPluginManagerBootstrapming(object): def test_plugin_prevent_register_stepwise_on_cacheprovider_unregister( self, pytestpm ): - """ From PR #4304 : The only way to unregister a module is documented at - the end of https://docs.pytest.org/en/latest/plugins.html. + """From PR #4304: The only way to unregister a module is documented at + the end of https://docs.pytest.org/en/stable/plugins.html. - When unregister cacheprovider, then unregister stepwise too + When unregister cacheprovider, then unregister stepwise too. """ pytestpm.register(42, name="cacheprovider") pytestpm.register(43, name="stepwise") diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py index f7cc0936cce..46fab0ce893 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_pytester.py @@ -1,36 +1,33 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import os import subprocess import sys import time +from typing import List import py.path import _pytest.pytester as pytester import pytest +from _pytest.config import ExitCode from _pytest.config import PytestPluginManager -from _pytest.main import EXIT_NOTESTSCOLLECTED -from _pytest.main import EXIT_OK -from _pytest.main import EXIT_TESTSFAILED from _pytest.pytester import CwdSnapshot from _pytest.pytester import HookRecorder from _pytest.pytester import LineMatcher from _pytest.pytester import SysModulesSnapshot from _pytest.pytester import SysPathsSnapshot +from _pytest.pytester import Testdir -def test_make_hook_recorder(testdir): +def test_make_hook_recorder(testdir) -> None: item = testdir.getitem("def test_func(): pass") recorder = testdir.make_hook_recorder(item.config.pluginmanager) assert not recorder.getfailures() - pytest.xfail("internal reportrecorder tests need refactoring") + # (The silly condition is to fool mypy that the code below this is reachable) + if 1 + 1 == 2: + pytest.xfail("internal reportrecorder tests need refactoring") - class rep(object): + class rep: excinfo = None passed = False failed = True @@ -43,23 +40,23 @@ def test_make_hook_recorder(testdir): failures = recorder.getfailures() assert failures == [rep] - class rep(object): + class rep2: excinfo = None passed = False failed = False skipped = True when = "call" - rep.passed = False - rep.skipped = True - recorder.hook.pytest_runtest_logreport(report=rep) + rep2.passed = False + rep2.skipped = True + recorder.hook.pytest_runtest_logreport(report=rep2) modcol = testdir.getmodulecol("") - rep = modcol.config.hook.pytest_make_collect_report(collector=modcol) - rep.passed = False - rep.failed = True - rep.skipped = False - recorder.hook.pytest_collectreport(report=rep) + rep3 = modcol.config.hook.pytest_make_collect_report(collector=modcol) + rep3.passed = False + rep3.failed = True + rep3.skipped = False + recorder.hook.pytest_collectreport(report=rep3) passed, skipped, failed = recorder.listoutcomes() assert not passed and skipped and failed @@ -72,18 +69,17 @@ def test_make_hook_recorder(testdir): recorder.unregister() recorder.clear() - recorder.hook.pytest_runtest_logreport(report=rep) + recorder.hook.pytest_runtest_logreport(report=rep3) pytest.raises(ValueError, recorder.getfailures) -def test_parseconfig(testdir): +def test_parseconfig(testdir) -> None: config1 = testdir.parseconfig() config2 = testdir.parseconfig() - assert config2 != config1 - assert config1 != pytest.config + assert config2 is not config1 -def test_testdir_runs_with_plugin(testdir): +def test_testdir_runs_with_plugin(testdir) -> None: testdir.makepyfile( """ pytest_plugins = "pytester" @@ -95,7 +91,30 @@ def test_testdir_runs_with_plugin(testdir): result.assert_outcomes(passed=1) -def test_runresult_assertion_on_xfail(testdir): +def test_testdir_with_doctest(testdir): + """Check that testdir can be used within doctests. + + It used to use `request.function`, which is `None` with doctests.""" + testdir.makepyfile( + **{ + "sub/t-doctest.py": """ + ''' + >>> import os + >>> testdir = getfixture("testdir") + >>> str(testdir.makepyfile("content")).replace(os.sep, '/') + '.../basetemp/sub.t-doctest0/sub.py' + ''' + """, + "sub/__init__.py": "", + } + ) + result = testdir.runpytest( + "-p", "pytester", "--doctest-modules", "sub/t-doctest.py" + ) + assert result.ret == 0 + + +def test_runresult_assertion_on_xfail(testdir) -> None: testdir.makepyfile( """ import pytest @@ -112,7 +131,7 @@ def test_runresult_assertion_on_xfail(testdir): assert result.ret == 0 -def test_runresult_assertion_on_xpassed(testdir): +def test_runresult_assertion_on_xpassed(testdir) -> None: testdir.makepyfile( """ import pytest @@ -129,18 +148,7 @@ def test_runresult_assertion_on_xpassed(testdir): assert result.ret == 0 -def test_runresult_repr(): - from _pytest.pytester import RunResult - - assert ( - repr( - RunResult(ret="ret", outlines=[""], errlines=["some", "errors"], duration=1) - ) - == "<RunResult ret='ret' len(stdout.lines)=1 len(stderr.lines)=2 duration=1.00s>" - ) - - -def test_xpassed_with_strict_is_considered_a_failure(testdir): +def test_xpassed_with_strict_is_considered_a_failure(testdir) -> None: testdir.makepyfile( """ import pytest @@ -158,28 +166,28 @@ def test_xpassed_with_strict_is_considered_a_failure(testdir): def make_holder(): - class apiclass(object): + class apiclass: def pytest_xyz(self, arg): - "x" + """X""" def pytest_xyz_noarg(self): - "x" + """X""" apimod = type(os)("api") def pytest_xyz(arg): - "x" + """X""" def pytest_xyz_noarg(): - "x" + """X""" - apimod.pytest_xyz = pytest_xyz - apimod.pytest_xyz_noarg = pytest_xyz_noarg + apimod.pytest_xyz = pytest_xyz # type: ignore + apimod.pytest_xyz_noarg = pytest_xyz_noarg # type: ignore return apiclass, apimod @pytest.mark.parametrize("holder", make_holder()) -def test_hookrecorder_basic(holder): +def test_hookrecorder_basic(holder) -> None: pm = PytestPluginManager() pm.add_hookspecs(holder) rec = HookRecorder(pm) @@ -193,42 +201,35 @@ def test_hookrecorder_basic(holder): assert call._name == "pytest_xyz_noarg" -def test_makepyfile_unicode(testdir): - global unichr - try: - unichr(65) - except NameError: - unichr = chr - testdir.makepyfile(unichr(0xFFFD)) +def test_makepyfile_unicode(testdir) -> None: + testdir.makepyfile(chr(0xFFFD)) -def test_makepyfile_utf8(testdir): +def test_makepyfile_utf8(testdir) -> None: """Ensure makepyfile accepts utf-8 bytes as input (#2738)""" - utf8_contents = u""" + utf8_contents = """ def setup_function(function): - mixed_encoding = u'São Paulo' - """.encode( - "utf-8" - ) + mixed_encoding = 'São Paulo' + """.encode() p = testdir.makepyfile(utf8_contents) - assert u"mixed_encoding = u'São Paulo'".encode("utf-8") in p.read("rb") + assert "mixed_encoding = 'São Paulo'".encode() in p.read("rb") -class TestInlineRunModulesCleanup(object): - def test_inline_run_test_module_not_cleaned_up(self, testdir): +class TestInlineRunModulesCleanup: + def test_inline_run_test_module_not_cleaned_up(self, testdir) -> None: test_mod = testdir.makepyfile("def test_foo(): assert True") result = testdir.inline_run(str(test_mod)) - assert result.ret == EXIT_OK + assert result.ret == ExitCode.OK # rewrite module, now test should fail if module was re-imported test_mod.write("def test_foo(): assert False") result2 = testdir.inline_run(str(test_mod)) - assert result2.ret == EXIT_TESTSFAILED + assert result2.ret == ExitCode.TESTS_FAILED def spy_factory(self): - class SysModulesSnapshotSpy(object): - instances = [] + class SysModulesSnapshotSpy: + instances = [] # type: List[SysModulesSnapshotSpy] - def __init__(self, preserve=None): + def __init__(self, preserve=None) -> None: SysModulesSnapshotSpy.instances.append(self) self._spy_restore_count = 0 self._spy_preserve = preserve @@ -242,7 +243,7 @@ class TestInlineRunModulesCleanup(object): def test_inline_run_taking_and_restoring_a_sys_modules_snapshot( self, testdir, monkeypatch - ): + ) -> None: spy_factory = self.spy_factory() monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) testdir.syspathinsert() @@ -263,7 +264,7 @@ class TestInlineRunModulesCleanup(object): def test_inline_run_sys_modules_snapshot_restore_preserving_modules( self, testdir, monkeypatch - ): + ) -> None: spy_factory = self.spy_factory() monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) test_mod = testdir.makepyfile("def test_foo(): pass") @@ -274,7 +275,7 @@ class TestInlineRunModulesCleanup(object): assert spy._spy_preserve("zope.interface") assert spy._spy_preserve("zopelicious") - def test_external_test_module_imports_not_cleaned_up(self, testdir): + def test_external_test_module_imports_not_cleaned_up(self, testdir) -> None: testdir.syspathinsert() testdir.makepyfile(imported="data = 'you son of a silly person'") import imported @@ -289,15 +290,16 @@ class TestInlineRunModulesCleanup(object): assert imported.data == 42 -def test_assert_outcomes_after_pytest_error(testdir): +def test_assert_outcomes_after_pytest_error(testdir) -> None: testdir.makepyfile("def test_foo(): assert True") result = testdir.runpytest("--unexpected-argument") - with pytest.raises(ValueError, match="Pytest terminal report not found"): + with pytest.raises(ValueError, match="Pytest terminal summary report not found"): result.assert_outcomes(passed=0) -def test_cwd_snapshot(tmpdir): +def test_cwd_snapshot(testdir: Testdir) -> None: + tmpdir = testdir.tmpdir foo = tmpdir.ensure("foo", dir=1) bar = tmpdir.ensure("bar", dir=1) foo.chdir() @@ -308,19 +310,19 @@ def test_cwd_snapshot(tmpdir): assert py.path.local() == foo -class TestSysModulesSnapshot(object): +class TestSysModulesSnapshot: key = "my-test-module" - def test_remove_added(self): + def test_remove_added(self) -> None: original = dict(sys.modules) assert self.key not in sys.modules snapshot = SysModulesSnapshot() - sys.modules[self.key] = "something" + sys.modules[self.key] = "something" # type: ignore assert self.key in sys.modules snapshot.restore() assert sys.modules == original - def test_add_removed(self, monkeypatch): + def test_add_removed(self, monkeypatch) -> None: assert self.key not in sys.modules monkeypatch.setitem(sys.modules, self.key, "something") assert self.key in sys.modules @@ -331,17 +333,17 @@ class TestSysModulesSnapshot(object): snapshot.restore() assert sys.modules == original - def test_restore_reloaded(self, monkeypatch): + def test_restore_reloaded(self, monkeypatch) -> None: assert self.key not in sys.modules monkeypatch.setitem(sys.modules, self.key, "something") assert self.key in sys.modules original = dict(sys.modules) snapshot = SysModulesSnapshot() - sys.modules[self.key] = "something else" + sys.modules[self.key] = "something else" # type: ignore snapshot.restore() assert sys.modules == original - def test_preserve_modules(self, monkeypatch): + def test_preserve_modules(self, monkeypatch) -> None: key = [self.key + str(i) for i in range(3)] assert not any(k in sys.modules for k in key) for i, k in enumerate(key): @@ -352,17 +354,17 @@ class TestSysModulesSnapshot(object): return name in (key[0], key[1], "some-other-key") snapshot = SysModulesSnapshot(preserve=preserve) - sys.modules[key[0]] = original[key[0]] = "something else0" - sys.modules[key[1]] = original[key[1]] = "something else1" - sys.modules[key[2]] = "something else2" + sys.modules[key[0]] = original[key[0]] = "something else0" # type: ignore + sys.modules[key[1]] = original[key[1]] = "something else1" # type: ignore + sys.modules[key[2]] = "something else2" # type: ignore snapshot.restore() assert sys.modules == original - def test_preserve_container(self, monkeypatch): + def test_preserve_container(self, monkeypatch) -> None: original = dict(sys.modules) assert self.key not in original replacement = dict(sys.modules) - replacement[self.key] = "life of brian" + replacement[self.key] = "life of brian" # type: ignore snapshot = SysModulesSnapshot() monkeypatch.setattr(sys, "modules", replacement) snapshot.restore() @@ -371,14 +373,14 @@ class TestSysModulesSnapshot(object): @pytest.mark.parametrize("path_type", ("path", "meta_path")) -class TestSysPathsSnapshot(object): +class TestSysPathsSnapshot: other_path = {"path": "meta_path", "meta_path": "path"} @staticmethod - def path(n): + def path(n: int) -> str: return "my-dirty-little-secret-" + str(n) - def test_restore(self, monkeypatch, path_type): + def test_restore(self, monkeypatch, path_type) -> None: other_path_type = self.other_path[path_type] for i in range(10): assert self.path(i) not in getattr(sys, path_type) @@ -401,12 +403,12 @@ class TestSysPathsSnapshot(object): assert getattr(sys, path_type) == original assert getattr(sys, other_path_type) == original_other - def test_preserve_container(self, monkeypatch, path_type): + def test_preserve_container(self, monkeypatch, path_type) -> None: other_path_type = self.other_path[path_type] original_data = list(getattr(sys, path_type)) original_other = getattr(sys, other_path_type) original_other_data = list(original_other) - new = [] + new = [] # type: List[object] snapshot = SysPathsSnapshot() monkeypatch.setattr(sys, path_type, new) snapshot.restore() @@ -416,22 +418,43 @@ class TestSysPathsSnapshot(object): assert getattr(sys, other_path_type) == original_other_data -def test_testdir_subprocess(testdir): +def test_testdir_subprocess(testdir) -> None: testfile = testdir.makepyfile("def test_one(): pass") assert testdir.runpytest_subprocess(testfile).ret == 0 -def test_unicode_args(testdir): - result = testdir.runpytest("-k", u"💩") - assert result.ret == EXIT_NOTESTSCOLLECTED +def test_testdir_subprocess_via_runpytest_arg(testdir) -> None: + testfile = testdir.makepyfile( + """ + def test_testdir_subprocess(testdir): + import os + testfile = testdir.makepyfile( + \""" + import os + def test_one(): + assert {} != os.getpid() + \""".format(os.getpid()) + ) + assert testdir.runpytest(testfile).ret == 0 + """ + ) + result = testdir.runpytest_subprocess( + "-p", "pytester", "--runpytest", "subprocess", testfile + ) + assert result.ret == 0 + + +def test_unicode_args(testdir) -> None: + result = testdir.runpytest("-k", "אבג") + assert result.ret == ExitCode.NO_TESTS_COLLECTED -def test_testdir_run_no_timeout(testdir): +def test_testdir_run_no_timeout(testdir) -> None: testfile = testdir.makepyfile("def test_no_timeout(): pass") - assert testdir.runpytest_subprocess(testfile).ret == EXIT_OK + assert testdir.runpytest_subprocess(testfile).ret == ExitCode.OK -def test_testdir_run_with_timeout(testdir): +def test_testdir_run_with_timeout(testdir) -> None: testfile = testdir.makepyfile("def test_no_timeout(): pass") timeout = 120 @@ -441,11 +464,11 @@ def test_testdir_run_with_timeout(testdir): end = time.time() duration = end - start - assert result.ret == EXIT_OK + assert result.ret == ExitCode.OK assert duration < timeout -def test_testdir_run_timeout_expires(testdir): +def test_testdir_run_timeout_expires(testdir) -> None: testfile = testdir.makepyfile( """ import time @@ -457,35 +480,147 @@ def test_testdir_run_timeout_expires(testdir): testdir.runpytest_subprocess(testfile, timeout=1) -def test_linematcher_with_nonlist(): +def test_linematcher_with_nonlist() -> None: """Test LineMatcher with regard to passing in a set (accidentally).""" - lm = LineMatcher([]) + from _pytest._code.source import Source - with pytest.raises(AssertionError): - lm.fnmatch_lines(set()) - with pytest.raises(AssertionError): - lm.fnmatch_lines({}) + lm = LineMatcher([]) + with pytest.raises(TypeError, match="invalid type for lines2: set"): + lm.fnmatch_lines(set()) # type: ignore[arg-type] + with pytest.raises(TypeError, match="invalid type for lines2: dict"): + lm.fnmatch_lines({}) # type: ignore[arg-type] + with pytest.raises(TypeError, match="invalid type for lines2: set"): + lm.re_match_lines(set()) # type: ignore[arg-type] + with pytest.raises(TypeError, match="invalid type for lines2: dict"): + lm.re_match_lines({}) # type: ignore[arg-type] + with pytest.raises(TypeError, match="invalid type for lines2: Source"): + lm.fnmatch_lines(Source()) # type: ignore[arg-type] lm.fnmatch_lines([]) lm.fnmatch_lines(()) + lm.fnmatch_lines("") + assert lm._getlines({}) == {} # type: ignore[arg-type,comparison-overlap] + assert lm._getlines(set()) == set() # type: ignore[arg-type,comparison-overlap] + assert lm._getlines(Source()) == [] + assert lm._getlines(Source("pass\npass")) == ["pass", "pass"] + + +def test_linematcher_match_failure() -> None: + lm = LineMatcher(["foo", "foo", "bar"]) + with pytest.raises(pytest.fail.Exception) as e: + lm.fnmatch_lines(["foo", "f*", "baz"]) + assert e.value.msg is not None + assert e.value.msg.splitlines() == [ + "exact match: 'foo'", + "fnmatch: 'f*'", + " with: 'foo'", + "nomatch: 'baz'", + " and: 'bar'", + "remains unmatched: 'baz'", + ] + + lm = LineMatcher(["foo", "foo", "bar"]) + with pytest.raises(pytest.fail.Exception) as e: + lm.re_match_lines(["foo", "^f.*", "baz"]) + assert e.value.msg is not None + assert e.value.msg.splitlines() == [ + "exact match: 'foo'", + "re.match: '^f.*'", + " with: 'foo'", + " nomatch: 'baz'", + " and: 'bar'", + "remains unmatched: 'baz'", + ] + + +def test_linematcher_consecutive(): + lm = LineMatcher(["1", "", "2"]) + with pytest.raises(pytest.fail.Exception) as excinfo: + lm.fnmatch_lines(["1", "2"], consecutive=True) + assert str(excinfo.value).splitlines() == [ + "exact match: '1'", + "no consecutive match: '2'", + " with: ''", + ] + + lm.re_match_lines(["1", r"\d?", "2"], consecutive=True) + with pytest.raises(pytest.fail.Exception) as excinfo: + lm.re_match_lines(["1", r"\d", "2"], consecutive=True) + assert str(excinfo.value).splitlines() == [ + "exact match: '1'", + r"no consecutive match: '\\d'", + " with: ''", + ] + + +@pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"]) +def test_linematcher_no_matching(function) -> None: + if function == "no_fnmatch_line": + good_pattern = "*.py OK*" + bad_pattern = "*X.py OK*" + else: + assert function == "no_re_match_line" + good_pattern = r".*py OK" + bad_pattern = r".*Xpy OK" + + lm = LineMatcher( + [ + "cachedir: .pytest_cache", + "collecting ... collected 1 item", + "", + "show_fixtures_per_test.py OK", + "=== elapsed 1s ===", + ] + ) - assert lm._getlines({}) == {} - assert lm._getlines(set()) == set() - - -def test_pytester_addopts(request, monkeypatch): + # check the function twice to ensure we don't accumulate the internal buffer + for i in range(2): + with pytest.raises(pytest.fail.Exception) as e: + func = getattr(lm, function) + func(good_pattern) + obtained = str(e.value).splitlines() + if function == "no_fnmatch_line": + assert obtained == [ + "nomatch: '{}'".format(good_pattern), + " and: 'cachedir: .pytest_cache'", + " and: 'collecting ... collected 1 item'", + " and: ''", + "fnmatch: '{}'".format(good_pattern), + " with: 'show_fixtures_per_test.py OK'", + ] + else: + assert obtained == [ + " nomatch: '{}'".format(good_pattern), + " and: 'cachedir: .pytest_cache'", + " and: 'collecting ... collected 1 item'", + " and: ''", + "re.match: '{}'".format(good_pattern), + " with: 'show_fixtures_per_test.py OK'", + ] + + func = getattr(lm, function) + func(bad_pattern) # bad pattern does not match any line: passes + + +def test_linematcher_no_matching_after_match() -> None: + lm = LineMatcher(["1", "2", "3"]) + lm.fnmatch_lines(["1", "3"]) + with pytest.raises(pytest.fail.Exception) as e: + lm.no_fnmatch_line("*") + assert str(e.value).splitlines() == ["fnmatch: '*'", " with: '1'"] + + +def test_pytester_addopts_before_testdir(request, monkeypatch) -> None: + orig = os.environ.get("PYTEST_ADDOPTS", None) monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused") - testdir = request.getfixturevalue("testdir") - - try: - assert "PYTEST_ADDOPTS" not in os.environ - finally: - testdir.finalize() - - assert os.environ["PYTEST_ADDOPTS"] == "--orig-unused" + assert "PYTEST_ADDOPTS" not in os.environ + testdir.finalize() + assert os.environ.get("PYTEST_ADDOPTS") == "--orig-unused" + monkeypatch.undo() + assert os.environ.get("PYTEST_ADDOPTS") == orig -def test_run_stdin(testdir): +def test_run_stdin(testdir) -> None: with pytest.raises(testdir.TimeoutExpired): testdir.run( sys.executable, @@ -515,7 +650,7 @@ def test_run_stdin(testdir): assert result.ret == 0 -def test_popen_stdin_pipe(testdir): +def test_popen_stdin_pipe(testdir) -> None: proc = testdir.popen( [sys.executable, "-c", "import sys; print(sys.stdin.read())"], stdout=subprocess.PIPE, @@ -529,7 +664,7 @@ def test_popen_stdin_pipe(testdir): assert proc.returncode == 0 -def test_popen_stdin_bytes(testdir): +def test_popen_stdin_bytes(testdir) -> None: proc = testdir.popen( [sys.executable, "-c", "import sys; print(sys.stdin.read())"], stdout=subprocess.PIPE, @@ -542,34 +677,41 @@ def test_popen_stdin_bytes(testdir): assert proc.returncode == 0 -def test_popen_default_stdin_stderr_and_stdin_None(testdir): +def test_popen_default_stdin_stderr_and_stdin_None(testdir) -> None: # stdout, stderr default to pipes, # stdin can be None to not close the pipe, avoiding # "ValueError: flush of closed file" with `communicate()`. + # + # Wraps the test to make it not hang when run with "-s". p1 = testdir.makepyfile( - """ + ''' import sys - print(sys.stdin.read()) # empty - print('stdout') - sys.stderr.write('stderr') - """ - ) - proc = testdir.popen([sys.executable, str(p1)], stdin=None) - stdout, stderr = proc.communicate(b"ignored") - assert stdout.splitlines() == [b"", b"stdout"] - assert stderr.splitlines() == [b"stderr"] - assert proc.returncode == 0 + def test_inner(testdir): + p1 = testdir.makepyfile( + """ + import sys + print(sys.stdin.read()) # empty + print('stdout') + sys.stderr.write('stderr') + """ + ) + proc = testdir.popen([sys.executable, str(p1)], stdin=None) + stdout, stderr = proc.communicate(b"ignored") + assert stdout.splitlines() == [b"", b"stdout"] + assert stderr.splitlines() == [b"stderr"] + assert proc.returncode == 0 + ''' + ) + result = testdir.runpytest("-p", "pytester", str(p1)) + assert result.ret == 0 -def test_spawn_uses_tmphome(testdir): - import os +def test_spawn_uses_tmphome(testdir) -> None: tmphome = str(testdir.tmpdir) + assert os.environ.get("HOME") == tmphome - # Does use HOME only during run. - assert os.environ.get("HOME") != tmphome - - testdir._env_run_update["CUSTOMENV"] = "42" + testdir.monkeypatch.setenv("CUSTOMENV", "42") p1 = testdir.makepyfile( """ @@ -585,3 +727,83 @@ def test_spawn_uses_tmphome(testdir): child = testdir.spawn_pytest(str(p1)) out = child.read() assert child.wait() == 0, out.decode("utf8") + + +def test_run_result_repr() -> None: + outlines = ["some", "normal", "output"] + errlines = ["some", "nasty", "errors", "happened"] + + # known exit code + r = pytester.RunResult(1, outlines, errlines, duration=0.5) + assert ( + repr(r) == "<RunResult ret=ExitCode.TESTS_FAILED len(stdout.lines)=3" + " len(stderr.lines)=4 duration=0.50s>" + ) + + # unknown exit code: just the number + r = pytester.RunResult(99, outlines, errlines, duration=0.5) + assert ( + repr(r) == "<RunResult ret=99 len(stdout.lines)=3" + " len(stderr.lines)=4 duration=0.50s>" + ) + + +def test_testdir_outcomes_with_multiple_errors(testdir): + p1 = testdir.makepyfile( + """ + import pytest + + @pytest.fixture + def bad_fixture(): + raise Exception("bad") + + def test_error1(bad_fixture): + pass + + def test_error2(bad_fixture): + pass + """ + ) + result = testdir.runpytest(str(p1)) + result.assert_outcomes(errors=2) + + assert result.parseoutcomes() == {"errors": 2} + + +def test_parse_summary_line_always_plural(): + """Parsing summaries always returns plural nouns (#6505)""" + lines = [ + "some output 1", + "some output 2", + "======= 1 failed, 1 passed, 1 warning, 1 error in 0.13s ====", + "done.", + ] + assert pytester.RunResult.parse_summary_nouns(lines) == { + "errors": 1, + "failed": 1, + "passed": 1, + "warnings": 1, + } + + lines = [ + "some output 1", + "some output 2", + "======= 1 failed, 1 passed, 2 warnings, 2 errors in 0.13s ====", + "done.", + ] + assert pytester.RunResult.parse_summary_nouns(lines) == { + "errors": 2, + "failed": 1, + "passed": 1, + "warnings": 2, + } + + +def test_makefile_joins_absolute_path(testdir: Testdir) -> None: + absfile = testdir.tmpdir / "absfile" + if sys.platform == "win32": + with pytest.raises(OSError): + testdir.makepyfile(**{str(absfile): ""}) + else: + p1 = testdir.makepyfile(**{str(absfile): ""}) + assert str(p1) == (testdir.tmpdir / absfile) + ".py" diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py index d652219df3e..f61f8586f9c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_recwarn.py @@ -1,23 +1,18 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import re import warnings +from typing import Optional import pytest from _pytest.recwarn import WarningsRecorder -from _pytest.warning_types import PytestDeprecationWarning -def test_recwarn_stacklevel(recwarn): +def test_recwarn_stacklevel(recwarn: WarningsRecorder) -> None: warnings.warn("hello") warn = recwarn.pop() assert warn.filename == __file__ -def test_recwarn_functional(testdir): +def test_recwarn_functional(testdir) -> None: testdir.makepyfile( """ import warnings @@ -31,8 +26,8 @@ def test_recwarn_functional(testdir): reprec.assertoutcome(passed=1) -class TestWarningsRecorderChecker(object): - def test_recording(self): +class TestWarningsRecorderChecker: + def test_recording(self) -> None: rec = WarningsRecorder() with rec: assert not rec.list @@ -48,23 +43,23 @@ class TestWarningsRecorderChecker(object): assert values is rec.list pytest.raises(AssertionError, rec.pop) - def test_warn_stacklevel(self): + def test_warn_stacklevel(self) -> None: """#4243""" rec = WarningsRecorder() with rec: warnings.warn("test", DeprecationWarning, 2) - def test_typechecking(self): + def test_typechecking(self) -> None: from _pytest.recwarn import WarningsChecker with pytest.raises(TypeError): - WarningsChecker(5) + WarningsChecker(5) # type: ignore with pytest.raises(TypeError): - WarningsChecker(("hi", RuntimeWarning)) + WarningsChecker(("hi", RuntimeWarning)) # type: ignore with pytest.raises(TypeError): - WarningsChecker([DeprecationWarning, RuntimeWarning]) + WarningsChecker([DeprecationWarning, RuntimeWarning]) # type: ignore - def test_invalid_enter_exit(self): + def test_invalid_enter_exit(self) -> None: # wrap this test in WarningsRecorder to ensure warning state gets reset with WarningsRecorder(): with pytest.raises(RuntimeError): @@ -78,53 +73,55 @@ class TestWarningsRecorderChecker(object): pass # can't enter twice -class TestDeprecatedCall(object): +class TestDeprecatedCall: """test pytest.deprecated_call()""" - def dep(self, i, j=None): + def dep(self, i: int, j: Optional[int] = None) -> int: if i == 0: warnings.warn("is deprecated", DeprecationWarning, stacklevel=1) return 42 - def dep_explicit(self, i): + def dep_explicit(self, i: int) -> None: if i == 0: warnings.warn_explicit( "dep_explicit", category=DeprecationWarning, filename="hello", lineno=3 ) - def test_deprecated_call_raises(self): + def test_deprecated_call_raises(self) -> None: with pytest.raises(pytest.fail.Exception, match="No warnings of type"): pytest.deprecated_call(self.dep, 3, 5) - def test_deprecated_call(self): + def test_deprecated_call(self) -> None: pytest.deprecated_call(self.dep, 0, 5) - def test_deprecated_call_ret(self): + def test_deprecated_call_ret(self) -> None: ret = pytest.deprecated_call(self.dep, 0) assert ret == 42 - def test_deprecated_call_preserves(self): - onceregistry = warnings.onceregistry.copy() - filters = warnings.filters[:] + def test_deprecated_call_preserves(self) -> None: + # Type ignored because `onceregistry` and `filters` are not + # documented API. + onceregistry = warnings.onceregistry.copy() # type: ignore + filters = warnings.filters[:] # type: ignore warn = warnings.warn warn_explicit = warnings.warn_explicit self.test_deprecated_call_raises() self.test_deprecated_call() - assert onceregistry == warnings.onceregistry - assert filters == warnings.filters + assert onceregistry == warnings.onceregistry # type: ignore + assert filters == warnings.filters # type: ignore assert warn is warnings.warn assert warn_explicit is warnings.warn_explicit - def test_deprecated_explicit_call_raises(self): + def test_deprecated_explicit_call_raises(self) -> None: with pytest.raises(pytest.fail.Exception): pytest.deprecated_call(self.dep_explicit, 3) - def test_deprecated_explicit_call(self): + def test_deprecated_explicit_call(self) -> None: pytest.deprecated_call(self.dep_explicit, 0) pytest.deprecated_call(self.dep_explicit, 0) @pytest.mark.parametrize("mode", ["context_manager", "call"]) - def test_deprecated_call_no_warning(self, mode): + def test_deprecated_call_no_warning(self, mode) -> None: """Ensure deprecated_call() raises the expected failure when its block/function does not raise a deprecation warning. """ @@ -146,7 +143,7 @@ class TestDeprecatedCall(object): @pytest.mark.parametrize("mode", ["context_manager", "call"]) @pytest.mark.parametrize("call_f_first", [True, False]) @pytest.mark.filterwarnings("ignore") - def test_deprecated_call_modes(self, warning_type, mode, call_f_first): + def test_deprecated_call_modes(self, warning_type, mode, call_f_first) -> None: """Ensure deprecated_call() captures a deprecation warning as expected inside its block/function. """ @@ -165,7 +162,7 @@ class TestDeprecatedCall(object): assert f() == 10 @pytest.mark.parametrize("mode", ["context_manager", "call"]) - def test_deprecated_call_exception_is_raised(self, mode): + def test_deprecated_call_exception_is_raised(self, mode) -> None: """If the block of the code being tested by deprecated_call() raises an exception, it must raise the exception undisturbed. """ @@ -180,7 +177,7 @@ class TestDeprecatedCall(object): with pytest.deprecated_call(): f() - def test_deprecated_call_specificity(self): + def test_deprecated_call_specificity(self) -> None: other_warnings = [ Warning, UserWarning, @@ -201,7 +198,7 @@ class TestDeprecatedCall(object): with pytest.deprecated_call(): f() - def test_deprecated_call_supports_match(self): + def test_deprecated_call_supports_match(self) -> None: with pytest.deprecated_call(match=r"must be \d+$"): warnings.warn("value must be 42", DeprecationWarning) @@ -210,30 +207,25 @@ class TestDeprecatedCall(object): warnings.warn("this is not here", DeprecationWarning) -class TestWarns(object): - def test_strings(self): +class TestWarns: + def test_check_callable(self) -> None: + source = "warnings.warn('w1', RuntimeWarning)" + with pytest.raises(TypeError, match=r".* must be callable"): + pytest.warns(RuntimeWarning, source) # type: ignore + + def test_several_messages(self) -> None: # different messages, b/c Python suppresses multiple identical warnings - source1 = "warnings.warn('w1', RuntimeWarning)" - source2 = "warnings.warn('w2', RuntimeWarning)" - source3 = "warnings.warn('w3', RuntimeWarning)" - with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg - pytest.warns(RuntimeWarning, source1) - pytest.raises( - pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2) - ) - pytest.warns(RuntimeWarning, source3) - assert len(warninfo) == 3 - for w in warninfo: - assert w.filename == __file__ - (msg,) = w.message.args - assert msg.startswith("warns(..., 'code(as_a_string)') is deprecated") - - def test_function(self): + pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning)) + with pytest.raises(pytest.fail.Exception): + pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning)) + pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning)) + + def test_function(self) -> None: pytest.warns( SyntaxWarning, lambda msg: warnings.warn(msg, SyntaxWarning), "syntax" ) - def test_warning_tuple(self): + def test_warning_tuple(self) -> None: pytest.warns( (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w1", RuntimeWarning) ) @@ -248,7 +240,7 @@ class TestWarns(object): ), ) - def test_as_contextmanager(self): + def test_as_contextmanager(self) -> None: with pytest.warns(RuntimeWarning): warnings.warn("runtime", RuntimeWarning) @@ -297,14 +289,14 @@ class TestWarns(object): ) ) - def test_record(self): + def test_record(self) -> None: with pytest.warns(UserWarning) as record: warnings.warn("user", UserWarning) assert len(record) == 1 assert str(record[0].message) == "user" - def test_record_only(self): + def test_record_only(self) -> None: with pytest.warns(None) as record: warnings.warn("user", UserWarning) warnings.warn("runtime", RuntimeWarning) @@ -313,7 +305,7 @@ class TestWarns(object): assert str(record[0].message) == "user" assert str(record[1].message) == "runtime" - def test_record_by_subclass(self): + def test_record_by_subclass(self) -> None: with pytest.warns(Warning) as record: warnings.warn("user", UserWarning) warnings.warn("runtime", RuntimeWarning) @@ -336,7 +328,7 @@ class TestWarns(object): assert str(record[0].message) == "user" assert str(record[1].message) == "runtime" - def test_double_test(self, testdir): + def test_double_test(self, testdir) -> None: """If a test is run again, the warning should still be raised""" testdir.makepyfile( """ @@ -352,7 +344,7 @@ class TestWarns(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["*2 passed in*"]) - def test_match_regex(self): + def test_match_regex(self) -> None: with pytest.warns(UserWarning, match=r"must be \d+$"): warnings.warn("value must be 42", UserWarning) @@ -364,24 +356,31 @@ class TestWarns(object): with pytest.warns(FutureWarning, match=r"must be \d+$"): warnings.warn("value must be 42", UserWarning) - def test_one_from_multiple_warns(self): + def test_one_from_multiple_warns(self) -> None: with pytest.warns(UserWarning, match=r"aaa"): warnings.warn("cccccccccc", UserWarning) warnings.warn("bbbbbbbbbb", UserWarning) warnings.warn("aaaaaaaaaa", UserWarning) - def test_none_of_multiple_warns(self): + def test_none_of_multiple_warns(self) -> None: with pytest.raises(pytest.fail.Exception): with pytest.warns(UserWarning, match=r"aaa"): warnings.warn("bbbbbbbbbb", UserWarning) warnings.warn("cccccccccc", UserWarning) @pytest.mark.filterwarnings("ignore") - def test_can_capture_previously_warned(self): - def f(): + def test_can_capture_previously_warned(self) -> None: + def f() -> int: warnings.warn(UserWarning("ohai")) return 10 assert f() == 10 assert pytest.warns(UserWarning, f) == 10 assert pytest.warns(UserWarning, f) == 10 + assert pytest.warns(UserWarning, f) != "10" # type: ignore[comparison-overlap] + + def test_warns_context_manager_with_kwargs(self) -> None: + with pytest.raises(TypeError) as excinfo: + with pytest.warns(UserWarning, foo="bar"): # type: ignore + pass + assert "Unexpected keyword arguments" in str(excinfo.value) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_reports.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_reports.py index 5c6851e49bd..dbe94896207 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_reports.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_reports.py @@ -1,14 +1,20 @@ -# -*- coding: utf-8 -*- +import sys +from typing import Sequence +from typing import Union + import pytest +from _pytest._code.code import ExceptionChainRepr +from _pytest._code.code import ExceptionRepr +from _pytest.config import Config from _pytest.pathlib import Path +from _pytest.pytester import Testdir from _pytest.reports import CollectReport from _pytest.reports import TestReport -class TestReportSerialization(object): - def test_xdist_longrepr_to_str_issue_241(self, testdir): - """ - Regarding issue pytest-xdist#241 +class TestReportSerialization: + def test_xdist_longrepr_to_str_issue_241(self, testdir: Testdir) -> None: + """Regarding issue pytest-xdist#241. This test came originally from test_remote.py in xdist (ca03269). """ @@ -30,7 +36,7 @@ class TestReportSerialization(object): assert test_b_call.outcome == "passed" assert test_b_call._to_json()["longrepr"] is None - def test_xdist_report_longrepr_reprcrash_130(self, testdir): + def test_xdist_report_longrepr_reprcrash_130(self, testdir: Testdir) -> None: """Regarding issue pytest-xdist#130 This test came originally from test_remote.py in xdist (ca03269). @@ -44,15 +50,19 @@ class TestReportSerialization(object): reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 rep = reports[1] - added_section = ("Failure Metadata", str("metadata metadata"), "*") + added_section = ("Failure Metadata", "metadata metadata", "*") + assert isinstance(rep.longrepr, ExceptionRepr) rep.longrepr.sections.append(added_section) d = rep._to_json() a = TestReport._from_json(d) + assert isinstance(a.longrepr, ExceptionRepr) # Check assembled == rep assert a.__dict__.keys() == rep.__dict__.keys() for key in rep.__dict__.keys(): if key != "longrepr": assert getattr(a, key) == getattr(rep, key) + assert rep.longrepr.reprcrash is not None + assert a.longrepr.reprcrash is not None assert rep.longrepr.reprcrash.lineno == a.longrepr.reprcrash.lineno assert rep.longrepr.reprcrash.message == a.longrepr.reprcrash.message assert rep.longrepr.reprcrash.path == a.longrepr.reprcrash.path @@ -65,7 +75,7 @@ class TestReportSerialization(object): # Missing section attribute PR171 assert added_section in a.longrepr.sections - def test_reprentries_serialization_170(self, testdir): + def test_reprentries_serialization_170(self, testdir: Testdir) -> None: """Regarding issue pytest-xdist#170 This test came originally from test_remote.py in xdist (ca03269). @@ -83,24 +93,35 @@ class TestReportSerialization(object): reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 rep = reports[1] + assert isinstance(rep.longrepr, ExceptionRepr) d = rep._to_json() a = TestReport._from_json(d) + assert isinstance(a.longrepr, ExceptionRepr) rep_entries = rep.longrepr.reprtraceback.reprentries a_entries = a.longrepr.reprtraceback.reprentries for i in range(len(a_entries)): - assert isinstance(rep_entries[i], ReprEntry) - assert rep_entries[i].lines == a_entries[i].lines - assert rep_entries[i].reprfileloc.lineno == a_entries[i].reprfileloc.lineno - assert ( - rep_entries[i].reprfileloc.message == a_entries[i].reprfileloc.message - ) - assert rep_entries[i].reprfileloc.path == a_entries[i].reprfileloc.path - assert rep_entries[i].reprfuncargs.args == a_entries[i].reprfuncargs.args - assert rep_entries[i].reprlocals.lines == a_entries[i].reprlocals.lines - assert rep_entries[i].style == a_entries[i].style + rep_entry = rep_entries[i] + assert isinstance(rep_entry, ReprEntry) + assert rep_entry.reprfileloc is not None + assert rep_entry.reprfuncargs is not None + assert rep_entry.reprlocals is not None + + a_entry = a_entries[i] + assert isinstance(a_entry, ReprEntry) + assert a_entry.reprfileloc is not None + assert a_entry.reprfuncargs is not None + assert a_entry.reprlocals is not None + + assert rep_entry.lines == a_entry.lines + assert rep_entry.reprfileloc.lineno == a_entry.reprfileloc.lineno + assert rep_entry.reprfileloc.message == a_entry.reprfileloc.message + assert rep_entry.reprfileloc.path == a_entry.reprfileloc.path + assert rep_entry.reprfuncargs.args == a_entry.reprfuncargs.args + assert rep_entry.reprlocals.lines == a_entry.reprlocals.lines + assert rep_entry.style == a_entry.style - def test_reprentries_serialization_196(self, testdir): + def test_reprentries_serialization_196(self, testdir: Testdir) -> None: """Regarding issue pytest-xdist#196 This test came originally from test_remote.py in xdist (ca03269). @@ -118,8 +139,10 @@ class TestReportSerialization(object): reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 rep = reports[1] + assert isinstance(rep.longrepr, ExceptionRepr) d = rep._to_json() a = TestReport._from_json(d) + assert isinstance(a.longrepr, ExceptionRepr) rep_entries = rep.longrepr.reprtraceback.reprentries a_entries = a.longrepr.reprtraceback.reprentries @@ -127,23 +150,21 @@ class TestReportSerialization(object): assert isinstance(rep_entries[i], ReprEntryNative) assert rep_entries[i].lines == a_entries[i].lines - def test_itemreport_outcomes(self, testdir): - """ - This test came originally from test_remote.py in xdist (ca03269). - """ + def test_itemreport_outcomes(self, testdir: Testdir) -> None: + # This test came originally from test_remote.py in xdist (ca03269). reprec = testdir.inline_runsource( """ - import py + import pytest def test_pass(): pass def test_fail(): 0/0 - @py.test.mark.skipif("True") + @pytest.mark.skipif("True") def test_skip(): pass def test_skip_imperative(): - py.test.skip("hello") - @py.test.mark.xfail("True") + pytest.skip("hello") + @pytest.mark.xfail("True") def test_xfail(): 0/0 def test_xfail_imperative(): - py.test.xfail("hello") + pytest.xfail("hello") """ ) reports = reprec.getreports("pytest_runtest_logreport") @@ -155,6 +176,7 @@ class TestReportSerialization(object): assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped if newrep.skipped and not hasattr(newrep, "wasxfail"): + assert isinstance(newrep.longrepr, tuple) assert len(newrep.longrepr) == 3 assert newrep.outcome == rep.outcome assert newrep.when == rep.when @@ -162,7 +184,7 @@ class TestReportSerialization(object): if rep.failed: assert newrep.longreprtext == rep.longreprtext - def test_collectreport_passed(self, testdir): + def test_collectreport_passed(self, testdir: Testdir) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" reprec = testdir.inline_runsource("def test_func(): pass") reports = reprec.getreports("pytest_collectreport") @@ -173,7 +195,7 @@ class TestReportSerialization(object): assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped - def test_collectreport_fail(self, testdir): + def test_collectreport_fail(self, testdir: Testdir) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" reprec = testdir.inline_runsource("qwe abc") reports = reprec.getreports("pytest_collectreport") @@ -187,13 +209,13 @@ class TestReportSerialization(object): if rep.failed: assert newrep.longrepr == str(rep.longrepr) - def test_extended_report_deserialization(self, testdir): + def test_extended_report_deserialization(self, testdir: Testdir) -> None: """This test came originally from test_remote.py in xdist (ca03269).""" reprec = testdir.inline_runsource("qwe abc") reports = reprec.getreports("pytest_collectreport") assert reports for rep in reports: - rep.extra = True + rep.extra = True # type: ignore[attr-defined] d = rep._to_json() newrep = CollectReport._from_json(d) assert newrep.extra @@ -203,7 +225,7 @@ class TestReportSerialization(object): if rep.failed: assert newrep.longrepr == str(rep.longrepr) - def test_paths_support(self, testdir): + def test_paths_support(self, testdir: Testdir) -> None: """Report attributes which are py.path or pathlib objects should become strings.""" testdir.makepyfile( """ @@ -215,14 +237,14 @@ class TestReportSerialization(object): reports = reprec.getreports("pytest_runtest_logreport") assert len(reports) == 3 test_a_call = reports[1] - test_a_call.path1 = testdir.tmpdir - test_a_call.path2 = Path(testdir.tmpdir) + test_a_call.path1 = testdir.tmpdir # type: ignore[attr-defined] + test_a_call.path2 = Path(testdir.tmpdir) # type: ignore[attr-defined] data = test_a_call._to_json() assert data["path1"] == str(testdir.tmpdir) assert data["path2"] == str(testdir.tmpdir) - def test_unserialization_failure(self, testdir): - """Check handling of failure during unserialization of report types.""" + def test_deserialization_failure(self, testdir: Testdir) -> None: + """Check handling of failure during deserialization of report types.""" testdir.makepyfile( """ def test_a(): @@ -243,11 +265,178 @@ class TestReportSerialization(object): ): TestReport._from_json(data) + @pytest.mark.parametrize("report_class", [TestReport, CollectReport]) + def test_chained_exceptions(self, testdir: Testdir, tw_mock, report_class) -> None: + """Check serialization/deserialization of report objects containing chained exceptions (#5786)""" + testdir.makepyfile( + """ + def foo(): + raise ValueError('value error') + def test_a(): + try: + foo() + except ValueError as e: + raise RuntimeError('runtime error') from e + if {error_during_import}: + test_a() + """.format( + error_during_import=report_class is CollectReport + ) + ) + + reprec = testdir.inline_run() + if report_class is TestReport: + reports = reprec.getreports( + "pytest_runtest_logreport" + ) # type: Union[Sequence[TestReport], Sequence[CollectReport]] + # we have 3 reports: setup/call/teardown + assert len(reports) == 3 + # get the call report + report = reports[1] + else: + assert report_class is CollectReport + # two collection reports: session and test file + reports = reprec.getreports("pytest_collectreport") + assert len(reports) == 2 + report = reports[1] + + def check_longrepr(longrepr: ExceptionChainRepr) -> None: + """Check the attributes of the given longrepr object according to the test file. + + We can get away with testing both CollectReport and TestReport with this function because + the longrepr objects are very similar. + """ + assert isinstance(longrepr, ExceptionChainRepr) + assert longrepr.sections == [("title", "contents", "=")] + assert len(longrepr.chain) == 2 + entry1, entry2 = longrepr.chain + tb1, fileloc1, desc1 = entry1 + tb2, fileloc2, desc2 = entry2 + + assert "ValueError('value error')" in str(tb1) + assert "RuntimeError('runtime error')" in str(tb2) + + assert ( + desc1 + == "The above exception was the direct cause of the following exception:" + ) + assert desc2 is None + + assert report.failed + assert len(report.sections) == 0 + assert isinstance(report.longrepr, ExceptionChainRepr) + report.longrepr.addsection("title", "contents", "=") + check_longrepr(report.longrepr) + + data = report._to_json() + loaded_report = report_class._from_json(data) + + assert loaded_report.failed + check_longrepr(loaded_report.longrepr) + + # make sure we don't blow up on ``toterminal`` call; we don't test the actual output because it is very + # brittle and hard to maintain, but we can assume it is correct because ``toterminal`` is already tested + # elsewhere and we do check the contents of the longrepr object after loading it. + loaded_report.longrepr.toterminal(tw_mock) + + def test_chained_exceptions_no_reprcrash(self, testdir: Testdir, tw_mock) -> None: + """Regression test for tracebacks without a reprcrash (#5971) + + This happens notably on exceptions raised by multiprocess.pool: the exception transfer + from subprocess to main process creates an artificial exception, which ExceptionInfo + can't obtain the ReprFileLocation from. + """ + # somehow in Python 3.5 on Windows this test fails with: + # File "c:\...\3.5.4\x64\Lib\multiprocessing\connection.py", line 302, in _recv_bytes + # overlapped=True) + # OSError: [WinError 6] The handle is invalid + # + # so in this platform we opted to use a mock traceback which is identical to the + # one produced by the multiprocessing module + if sys.version_info[:2] <= (3, 5) and sys.platform.startswith("win"): + testdir.makepyfile( + """ + # equivalent of multiprocessing.pool.RemoteTraceback + class RemoteTraceback(Exception): + def __init__(self, tb): + self.tb = tb + def __str__(self): + return self.tb + def test_a(): + try: + raise ValueError('value error') + except ValueError as e: + # equivalent to how multiprocessing.pool.rebuild_exc does it + e.__cause__ = RemoteTraceback('runtime error') + raise e + """ + ) + else: + testdir.makepyfile( + """ + from concurrent.futures import ProcessPoolExecutor + + def func(): + raise ValueError('value error') + + def test_a(): + with ProcessPoolExecutor() as p: + p.submit(func).result() + """ + ) + + testdir.syspathinsert() + reprec = testdir.inline_run() + + reports = reprec.getreports("pytest_runtest_logreport") + + def check_longrepr(longrepr: object) -> None: + assert isinstance(longrepr, ExceptionChainRepr) + assert len(longrepr.chain) == 2 + entry1, entry2 = longrepr.chain + tb1, fileloc1, desc1 = entry1 + tb2, fileloc2, desc2 = entry2 + + assert "RemoteTraceback" in str(tb1) + assert "ValueError: value error" in str(tb2) + + assert fileloc1 is None + assert fileloc2 is not None + assert fileloc2.message == "ValueError: value error" + + # 3 reports: setup/call/teardown: get the call report + assert len(reports) == 3 + report = reports[1] + + assert report.failed + check_longrepr(report.longrepr) + + data = report._to_json() + loaded_report = TestReport._from_json(data) + + assert loaded_report.failed + check_longrepr(loaded_report.longrepr) + + # for same reasons as previous test, ensure we don't blow up here + assert loaded_report.longrepr is not None + assert isinstance(loaded_report.longrepr, ExceptionChainRepr) + loaded_report.longrepr.toterminal(tw_mock) + + def test_report_prevent_ConftestImportFailure_hiding_exception( + self, testdir: Testdir + ) -> None: + sub_dir = testdir.tmpdir.join("ns").ensure_dir() + sub_dir.join("conftest").new(ext=".py").write("import unknown") + + result = testdir.runpytest_subprocess(".") + result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) + result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") + class TestHooks: """Test that the hooks are working correctly for plugins""" - def test_test_report(self, testdir, pytestconfig): + def test_test_report(self, testdir: Testdir, pytestconfig: Config) -> None: testdir.makepyfile( """ def test_a(): assert False @@ -261,7 +450,7 @@ class TestHooks: data = pytestconfig.hook.pytest_report_to_serializable( config=pytestconfig, report=rep ) - assert data["_report_type"] == "TestReport" + assert data["$report_type"] == "TestReport" new_rep = pytestconfig.hook.pytest_report_from_serializable( config=pytestconfig, data=data ) @@ -269,7 +458,7 @@ class TestHooks: assert new_rep.when == rep.when assert new_rep.outcome == rep.outcome - def test_collect_report(self, testdir, pytestconfig): + def test_collect_report(self, testdir: Testdir, pytestconfig: Config) -> None: testdir.makepyfile( """ def test_a(): assert False @@ -283,7 +472,7 @@ class TestHooks: data = pytestconfig.hook.pytest_report_to_serializable( config=pytestconfig, report=rep ) - assert data["_report_type"] == "CollectReport" + assert data["$report_type"] == "CollectReport" new_rep = pytestconfig.hook.pytest_report_from_serializable( config=pytestconfig, data=data ) @@ -294,7 +483,9 @@ class TestHooks: @pytest.mark.parametrize( "hook_name", ["pytest_runtest_logreport", "pytest_collectreport"] ) - def test_invalid_report_types(self, testdir, pytestconfig, hook_name): + def test_invalid_report_types( + self, testdir: Testdir, pytestconfig: Config, hook_name: str + ) -> None: testdir.makepyfile( """ def test_a(): pass @@ -307,7 +498,7 @@ class TestHooks: data = pytestconfig.hook.pytest_report_to_serializable( config=pytestconfig, report=rep ) - data["_report_type"] = "Unknown" + data["$report_type"] = "Unknown" with pytest.raises(AssertionError): _ = pytestconfig.hook.pytest_report_from_serializable( config=pytestconfig, data=data diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_resultlog.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_resultlog.py deleted file mode 100644 index 1ef49d5e2d9..00000000000 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_resultlog.py +++ /dev/null @@ -1,221 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os - -import py - -import _pytest._code -import pytest -from _pytest.resultlog import pytest_configure -from _pytest.resultlog import pytest_unconfigure -from _pytest.resultlog import ResultLog - -pytestmark = pytest.mark.filterwarnings("ignore:--result-log is deprecated") - - -def test_write_log_entry(): - reslog = ResultLog(None, None) - reslog.logfile = py.io.TextIO() - reslog.write_log_entry("name", ".", "") - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 1 - assert entry_lines[0] == ". name" - - reslog.logfile = py.io.TextIO() - reslog.write_log_entry("name", "s", "Skipped") - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 2 - assert entry_lines[0] == "s name" - assert entry_lines[1] == " Skipped" - - reslog.logfile = py.io.TextIO() - reslog.write_log_entry("name", "s", "Skipped\n") - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 2 - assert entry_lines[0] == "s name" - assert entry_lines[1] == " Skipped" - - reslog.logfile = py.io.TextIO() - longrepr = " tb1\n tb 2\nE tb3\nSome Error" - reslog.write_log_entry("name", "F", longrepr) - entry = reslog.logfile.getvalue() - assert entry[-1] == "\n" - entry_lines = entry.splitlines() - assert len(entry_lines) == 5 - assert entry_lines[0] == "F name" - assert entry_lines[1:] == [" " + line for line in longrepr.splitlines()] - - -class TestWithFunctionIntegration(object): - # XXX (hpk) i think that the resultlog plugin should - # provide a Parser object so that one can remain - # ignorant regarding formatting details. - def getresultlog(self, testdir, arg): - resultlog = testdir.tmpdir.join("resultlog") - testdir.plugins.append("resultlog") - args = ["--resultlog=%s" % resultlog] + [arg] - testdir.runpytest(*args) - return [x for x in resultlog.readlines(cr=0) if x] - - def test_collection_report(self, testdir): - ok = testdir.makepyfile(test_collection_ok="") - fail = testdir.makepyfile(test_collection_fail="XXX") - lines = self.getresultlog(testdir, ok) - assert not lines - - lines = self.getresultlog(testdir, fail) - assert lines - assert lines[0].startswith("F ") - assert lines[0].endswith("test_collection_fail.py"), lines[0] - for x in lines[1:]: - assert x.startswith(" ") - assert "XXX" in "".join(lines[1:]) - - def test_log_test_outcomes(self, testdir): - mod = testdir.makepyfile( - test_mod=""" - import pytest - def test_pass(): pass - def test_skip(): pytest.skip("hello") - def test_fail(): raise ValueError("FAIL") - - @pytest.mark.xfail - def test_xfail(): raise ValueError("XFAIL") - @pytest.mark.xfail - def test_xpass(): pass - - """ - ) - lines = self.getresultlog(testdir, mod) - assert len(lines) >= 3 - assert lines[0].startswith(". ") - assert lines[0].endswith("test_pass") - assert lines[1].startswith("s "), lines[1] - assert lines[1].endswith("test_skip") - assert lines[2].find("hello") != -1 - - assert lines[3].startswith("F ") - assert lines[3].endswith("test_fail") - tb = "".join(lines[4:8]) - assert tb.find('raise ValueError("FAIL")') != -1 - - assert lines[8].startswith("x ") - tb = "".join(lines[8:14]) - assert tb.find('raise ValueError("XFAIL")') != -1 - - assert lines[14].startswith("X ") - assert len(lines) == 15 - - @pytest.mark.parametrize("style", ("native", "long", "short")) - def test_internal_exception(self, style): - # they are produced for example by a teardown failing - # at the end of the run or a failing hook invocation - try: - raise ValueError - except ValueError: - excinfo = _pytest._code.ExceptionInfo.from_current() - reslog = ResultLog(None, py.io.TextIO()) - reslog.pytest_internalerror(excinfo.getrepr(style=style)) - entry = reslog.logfile.getvalue() - entry_lines = entry.splitlines() - - assert entry_lines[0].startswith("! ") - if style != "native": - assert os.path.basename(__file__)[:-9] in entry_lines[0] # .pyc/class - assert entry_lines[-1][0] == " " - assert "ValueError" in entry - - -def test_generic(testdir, LineMatcher): - testdir.plugins.append("resultlog") - testdir.makepyfile( - """ - import pytest - def test_pass(): - pass - def test_fail(): - assert 0 - def test_skip(): - pytest.skip("") - @pytest.mark.xfail - def test_xfail(): - assert 0 - @pytest.mark.xfail(run=False) - def test_xfail_norun(): - assert 0 - """ - ) - testdir.runpytest("--resultlog=result.log") - lines = testdir.tmpdir.join("result.log").readlines(cr=0) - LineMatcher(lines).fnmatch_lines( - [ - ". *:test_pass", - "F *:test_fail", - "s *:test_skip", - "x *:test_xfail", - "x *:test_xfail_norun", - ] - ) - - -def test_makedir_for_resultlog(testdir, LineMatcher): - """--resultlog should automatically create directories for the log file""" - testdir.plugins.append("resultlog") - testdir.makepyfile( - """ - import pytest - def test_pass(): - pass - """ - ) - testdir.runpytest("--resultlog=path/to/result.log") - lines = testdir.tmpdir.join("path/to/result.log").readlines(cr=0) - LineMatcher(lines).fnmatch_lines([". *:test_pass"]) - - -def test_no_resultlog_on_slaves(testdir): - config = testdir.parseconfig("-p", "resultlog", "--resultlog=resultlog") - - assert not hasattr(config, "_resultlog") - pytest_configure(config) - assert hasattr(config, "_resultlog") - pytest_unconfigure(config) - assert not hasattr(config, "_resultlog") - - config.slaveinput = {} - pytest_configure(config) - assert not hasattr(config, "_resultlog") - pytest_unconfigure(config) - assert not hasattr(config, "_resultlog") - - -def test_failure_issue380(testdir): - testdir.makeconftest( - """ - import pytest - class MyCollector(pytest.File): - def collect(self): - raise ValueError() - def repr_failure(self, excinfo): - return "somestring" - def pytest_collect_file(path, parent): - return MyCollector(parent=parent, fspath=path) - """ - ) - testdir.makepyfile( - """ - def test_func(): - pass - """ - ) - result = testdir.runpytest("--resultlog=log") - assert result.ret == 2 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner.py index 6906efb910c..b9d22370a7b 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner.py @@ -1,25 +1,28 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import inspect import os import sys import types +from typing import Dict +from typing import List +from typing import Tuple import py import _pytest._code import pytest -from _pytest import main from _pytest import outcomes from _pytest import reports from _pytest import runner +from _pytest.compat import TYPE_CHECKING +from _pytest.config import ExitCode +from _pytest.outcomes import OutcomeException + +if TYPE_CHECKING: + from typing import Type -class TestSetupState(object): - def test_setup(self, testdir): +class TestSetupState: + def test_setup(self, testdir) -> None: ss = runner.SetupState() item = testdir.getitem("def test_func(): pass") values = [1] @@ -29,14 +32,14 @@ class TestSetupState(object): ss._pop_and_teardown() assert not values - def test_teardown_exact_stack_empty(self, testdir): + def test_teardown_exact_stack_empty(self, testdir) -> None: item = testdir.getitem("def test_func(): pass") ss = runner.SetupState() ss.teardown_exact(item, None) ss.teardown_exact(item, None) ss.teardown_exact(item, None) - def test_setup_fails_and_failure_is_cached(self, testdir): + def test_setup_fails_and_failure_is_cached(self, testdir) -> None: item = testdir.getitem( """ def setup_module(mod): @@ -48,7 +51,7 @@ class TestSetupState(object): pytest.raises(ValueError, lambda: ss.prepare(item)) pytest.raises(ValueError, lambda: ss.prepare(item)) - def test_teardown_multiple_one_fails(self, testdir): + def test_teardown_multiple_one_fails(self, testdir) -> None: r = [] def fin1(): @@ -70,7 +73,7 @@ class TestSetupState(object): assert err.value.args == ("oops",) assert r == ["fin3", "fin1"] - def test_teardown_multiple_fail(self, testdir): + def test_teardown_multiple_fail(self, testdir) -> None: # Ensure the first exception is the one which is re-raised. # Ideally both would be reported however. def fin1(): @@ -87,7 +90,7 @@ class TestSetupState(object): ss._callfinalizers(item) assert err.value.args == ("oops2",) - def test_teardown_multiple_scopes_one_fails(self, testdir): + def test_teardown_multiple_scopes_one_fails(self, testdir) -> None: module_teardown = [] def fin_func(): @@ -106,8 +109,8 @@ class TestSetupState(object): assert module_teardown -class BaseFunctionalTests(object): - def test_passfunction(self, testdir): +class BaseFunctionalTests: + def test_passfunction(self, testdir) -> None: reports = testdir.runitem( """ def test_func(): @@ -120,7 +123,7 @@ class BaseFunctionalTests(object): assert rep.outcome == "passed" assert not rep.longrepr - def test_failfunction(self, testdir): + def test_failfunction(self, testdir) -> None: reports = testdir.runitem( """ def test_func(): @@ -135,7 +138,7 @@ class BaseFunctionalTests(object): assert rep.outcome == "failed" # assert isinstance(rep.longrepr, ReprExceptionInfo) - def test_skipfunction(self, testdir): + def test_skipfunction(self, testdir) -> None: reports = testdir.runitem( """ import pytest @@ -155,7 +158,7 @@ class BaseFunctionalTests(object): # assert rep.skipped.location.path # assert not rep.skipped.failurerepr - def test_skip_in_setup_function(self, testdir): + def test_skip_in_setup_function(self, testdir) -> None: reports = testdir.runitem( """ import pytest @@ -176,7 +179,7 @@ class BaseFunctionalTests(object): assert len(reports) == 2 assert reports[1].passed # teardown - def test_failure_in_setup_function(self, testdir): + def test_failure_in_setup_function(self, testdir) -> None: reports = testdir.runitem( """ import pytest @@ -193,7 +196,7 @@ class BaseFunctionalTests(object): assert rep.when == "setup" assert len(reports) == 2 - def test_failure_in_teardown_function(self, testdir): + def test_failure_in_teardown_function(self, testdir) -> None: reports = testdir.runitem( """ import pytest @@ -213,7 +216,7 @@ class BaseFunctionalTests(object): # assert rep.longrepr.reprcrash.lineno == 3 # assert rep.longrepr.reprtraceback.reprentries - def test_custom_failure_repr(self, testdir): + def test_custom_failure_repr(self, testdir) -> None: testdir.makepyfile( conftest=""" import pytest @@ -238,7 +241,7 @@ class BaseFunctionalTests(object): # assert rep.failed.where.path.basename == "test_func.py" # assert rep.failed.failurerepr == "hello" - def test_teardown_final_returncode(self, testdir): + def test_teardown_final_returncode(self, testdir) -> None: rec = testdir.inline_runsource( """ def test_func(): @@ -249,7 +252,7 @@ class BaseFunctionalTests(object): ) assert rec.ret == 1 - def test_logstart_logfinish_hooks(self, testdir): + def test_logstart_logfinish_hooks(self, testdir) -> None: rec = testdir.inline_runsource( """ import pytest @@ -266,7 +269,7 @@ class BaseFunctionalTests(object): assert rep.nodeid == "test_logstart_logfinish_hooks.py::test_func" assert rep.location == ("test_logstart_logfinish_hooks.py", 1, "test_func") - def test_exact_teardown_issue90(self, testdir): + def test_exact_teardown_issue90(self, testdir) -> None: rec = testdir.inline_runsource( """ import pytest @@ -306,8 +309,8 @@ class BaseFunctionalTests(object): assert reps[5].nodeid.endswith("test_func") assert reps[5].failed - def test_exact_teardown_issue1206(self, testdir): - """issue shadowing error with wrong number of arguments on teardown_method.""" + def test_exact_teardown_issue1206(self, testdir) -> None: + """Issue shadowing error with wrong number of arguments on teardown_method.""" rec = testdir.inline_runsource( """ import pytest @@ -342,7 +345,7 @@ class BaseFunctionalTests(object): "TypeError: teardown_method() takes exactly 4 arguments (2 given)", ) - def test_failure_in_setup_function_ignores_custom_repr(self, testdir): + def test_failure_in_setup_function_ignores_custom_repr(self, testdir) -> None: testdir.makepyfile( conftest=""" import pytest @@ -370,7 +373,7 @@ class BaseFunctionalTests(object): # assert rep.outcome.where.path.basename == "test_func.py" # assert instanace(rep.failed.failurerepr, PythonFailureRepr) - def test_systemexit_does_not_bail_out(self, testdir): + def test_systemexit_does_not_bail_out(self, testdir) -> None: try: reports = testdir.runitem( """ @@ -379,12 +382,12 @@ class BaseFunctionalTests(object): """ ) except SystemExit: - pytest.fail("runner did not catch SystemExit") + assert False, "runner did not catch SystemExit" rep = reports[1] assert rep.failed assert rep.when == "call" - def test_exit_propagates(self, testdir): + def test_exit_propagates(self, testdir) -> None: try: testdir.runitem( """ @@ -396,7 +399,7 @@ class BaseFunctionalTests(object): except pytest.exit.Exception: pass else: - pytest.fail("did not raise") + assert False, "did not raise" class TestExecutionNonForked(BaseFunctionalTests): @@ -406,7 +409,7 @@ class TestExecutionNonForked(BaseFunctionalTests): return f - def test_keyboardinterrupt_propagates(self, testdir): + def test_keyboardinterrupt_propagates(self, testdir) -> None: try: testdir.runitem( """ @@ -417,32 +420,11 @@ class TestExecutionNonForked(BaseFunctionalTests): except KeyboardInterrupt: pass else: - pytest.fail("did not raise") - + assert False, "did not raise" -class TestExecutionForked(BaseFunctionalTests): - pytestmark = pytest.mark.skipif("not hasattr(os, 'fork')") - - def getrunner(self): - # XXX re-arrange this test to live in pytest-xdist - boxed = pytest.importorskip("xdist.boxed") - return boxed.forked_run_report - def test_suicide(self, testdir): - reports = testdir.runitem( - """ - def test_func(): - import os - os.kill(os.getpid(), 15) - """ - ) - rep = reports[0] - assert rep.failed - assert rep.when == "???" - - -class TestSessionReports(object): - def test_collect_result(self, testdir): +class TestSessionReports: + def test_collect_result(self, testdir) -> None: col = testdir.getmodulecol( """ def test_func1(): @@ -465,35 +447,45 @@ class TestSessionReports(object): assert res[1].name == "TestClass" -reporttypes = [reports.BaseReport, reports.TestReport, reports.CollectReport] +reporttypes = [ + reports.BaseReport, + reports.TestReport, + reports.CollectReport, +] # type: List[Type[reports.BaseReport]] @pytest.mark.parametrize( "reporttype", reporttypes, ids=[x.__name__ for x in reporttypes] ) -def test_report_extra_parameters(reporttype): - if hasattr(inspect, "signature"): - args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] - else: - args = inspect.getargspec(reporttype.__init__)[0][1:] - basekw = dict.fromkeys(args, []) +def test_report_extra_parameters(reporttype: "Type[reports.BaseReport]") -> None: + args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] + basekw = dict.fromkeys(args, []) # type: Dict[str, List[object]] report = reporttype(newthing=1, **basekw) assert report.newthing == 1 -def test_callinfo(): - ci = runner.CallInfo.from_call(lambda: 0, "123") - assert ci.when == "123" +def test_callinfo() -> None: + ci = runner.CallInfo.from_call(lambda: 0, "collect") + assert ci.when == "collect" assert ci.result == 0 assert "result" in repr(ci) - assert repr(ci) == "<CallInfo when='123' result: 0>" + assert repr(ci) == "<CallInfo when='collect' result: 0>" + assert str(ci) == "<CallInfo when='collect' result: 0>" + + ci2 = runner.CallInfo.from_call(lambda: 0 / 0, "collect") + assert ci2.when == "collect" + assert not hasattr(ci2, "result") + assert repr(ci2) == "<CallInfo when='collect' excinfo={!r}>".format(ci2.excinfo) + assert str(ci2) == repr(ci2) + assert ci2.excinfo - ci = runner.CallInfo.from_call(lambda: 0 / 0, "123") - assert ci.when == "123" - assert not hasattr(ci, "result") - assert repr(ci) == "<CallInfo when='123' exception: division by zero>" - assert ci.excinfo - assert "exc" in repr(ci) + # Newlines are escaped. + def raise_assertion(): + assert 0, "assert_msg" + + ci3 = runner.CallInfo.from_call(raise_assertion, "call") + assert repr(ci3) == "<CallInfo when='call' excinfo={!r}>".format(ci3.excinfo) + assert "\n" not in repr(ci3) # design question: do we want general hooks in python files? @@ -501,7 +493,7 @@ def test_callinfo(): @pytest.mark.xfail -def test_runtest_in_module_ordering(testdir): +def test_runtest_in_module_ordering(testdir) -> None: p1 = testdir.makepyfile( """ import pytest @@ -514,9 +506,10 @@ def test_runtest_in_module_ordering(testdir): @pytest.fixture def mylist(self, request): return request.function.mylist - def pytest_runtest_call(self, item, __multicall__): + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_call(self, item): try: - __multicall__.execute() + (yield).get_result() except ValueError: pass def test_hello1(self, mylist): @@ -532,33 +525,33 @@ def test_runtest_in_module_ordering(testdir): result.stdout.fnmatch_lines(["*2 passed*"]) -def test_outcomeexception_exceptionattributes(): +def test_outcomeexception_exceptionattributes() -> None: outcome = outcomes.OutcomeException("test") assert outcome.args[0] == outcome.msg -def test_outcomeexception_passes_except_Exception(): +def test_outcomeexception_passes_except_Exception() -> None: with pytest.raises(outcomes.OutcomeException): try: raise outcomes.OutcomeException("test") - except Exception: - pass + except Exception as e: + raise NotImplementedError from e -def test_pytest_exit(): +def test_pytest_exit() -> None: with pytest.raises(pytest.exit.Exception) as excinfo: pytest.exit("hello") assert excinfo.errisinstance(pytest.exit.Exception) -def test_pytest_fail(): +def test_pytest_fail() -> None: with pytest.raises(pytest.fail.Exception) as excinfo: pytest.fail("hello") s = excinfo.exconly(tryshort=True) assert s.startswith("Failed") -def test_pytest_exit_msg(testdir): +def test_pytest_exit_msg(testdir) -> None: testdir.makeconftest( """ import pytest @@ -571,9 +564,19 @@ def test_pytest_exit_msg(testdir): result.stderr.fnmatch_lines(["Exit: oh noes"]) -def test_pytest_exit_returncode(testdir): +def _strip_resource_warnings(lines): + # Assert no output on stderr, except for unreliable ResourceWarnings. + # (https://github.com/pytest-dev/pytest/issues/5088) + return [ + x + for x in lines + if not x.startswith(("Exception ignored in:", "ResourceWarning")) + ] + + +def test_pytest_exit_returncode(testdir) -> None: testdir.makepyfile( - """ + """\ import pytest def test_foo(): pytest.exit("some exit msg", 99) @@ -581,19 +584,13 @@ def test_pytest_exit_returncode(testdir): ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*! *Exit: some exit msg !*"]) - # Assert no output on stderr, except for unreliable ResourceWarnings. - # (https://github.com/pytest-dev/pytest/issues/5088) - assert [ - x - for x in result.stderr.lines - if not x.startswith("Exception ignored in:") - and not x.startswith("ResourceWarning") - ] == [""] + + assert _strip_resource_warnings(result.stderr.lines) == [] assert result.ret == 99 # It prints to stderr also in case of exit during pytest_sessionstart. testdir.makeconftest( - """ + """\ import pytest def pytest_sessionstart(): @@ -602,11 +599,13 @@ def test_pytest_exit_returncode(testdir): ) result = testdir.runpytest() result.stdout.fnmatch_lines(["*! *Exit: during_sessionstart !*"]) - assert result.stderr.lines == ["Exit: during_sessionstart", ""] + assert _strip_resource_warnings(result.stderr.lines) == [ + "Exit: during_sessionstart" + ] assert result.ret == 98 -def test_pytest_fail_notrace_runtest(testdir): +def test_pytest_fail_notrace_runtest(testdir) -> None: """Test pytest.fail(..., pytrace=False) does not show tracebacks during test run.""" testdir.makepyfile( """ @@ -619,10 +618,10 @@ def test_pytest_fail_notrace_runtest(testdir): ) result = testdir.runpytest() result.stdout.fnmatch_lines(["world", "hello"]) - assert "def teardown_function" not in result.stdout.str() + result.stdout.no_fnmatch_line("*def teardown_function*") -def test_pytest_fail_notrace_collection(testdir): +def test_pytest_fail_notrace_collection(testdir) -> None: """Test pytest.fail(..., pytrace=False) does not show tracebacks during collection.""" testdir.makepyfile( """ @@ -634,37 +633,31 @@ def test_pytest_fail_notrace_collection(testdir): ) result = testdir.runpytest() result.stdout.fnmatch_lines(["hello"]) - assert "def some_internal_function()" not in result.stdout.str() + result.stdout.no_fnmatch_line("*def some_internal_function()*") -@pytest.mark.parametrize("str_prefix", ["u", ""]) -def test_pytest_fail_notrace_non_ascii(testdir, str_prefix): +def test_pytest_fail_notrace_non_ascii(testdir) -> None: """Fix pytest.fail with pytrace=False with non-ascii characters (#1178). This tests with native and unicode strings containing non-ascii chars. """ testdir.makepyfile( - u""" - # -*- coding: utf-8 -*- + """\ import pytest def test_hello(): - pytest.fail(%s'oh oh: ☺', pytrace=False) - """ - % str_prefix + pytest.fail('oh oh: ☺', pytrace=False) + """ ) result = testdir.runpytest() - if sys.version_info[0] >= 3: - result.stdout.fnmatch_lines(["*test_hello*", "oh oh: ☺"]) - else: - result.stdout.fnmatch_lines(["*test_hello*", "oh oh: *"]) - assert "def test_hello" not in result.stdout.str() + result.stdout.fnmatch_lines(["*test_hello*", "oh oh: ☺"]) + result.stdout.no_fnmatch_line("*def test_hello*") -def test_pytest_no_tests_collected_exit_status(testdir): +def test_pytest_no_tests_collected_exit_status(testdir) -> None: result = testdir.runpytest() result.stdout.fnmatch_lines(["*collected 0 items*"]) - assert result.ret == main.EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED testdir.makepyfile( test_foo=""" @@ -675,15 +668,16 @@ def test_pytest_no_tests_collected_exit_status(testdir): result = testdir.runpytest() result.stdout.fnmatch_lines(["*collected 1 item*"]) result.stdout.fnmatch_lines(["*1 passed*"]) - assert result.ret == main.EXIT_OK + assert result.ret == ExitCode.OK result = testdir.runpytest("-k nonmatch") result.stdout.fnmatch_lines(["*collected 1 item*"]) result.stdout.fnmatch_lines(["*1 deselected*"]) - assert result.ret == main.EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED -def test_exception_printing_skip(): +def test_exception_printing_skip() -> None: + assert pytest.skip.Exception == pytest.skip.Exception try: pytest.skip("hello") except pytest.skip.Exception: @@ -692,7 +686,7 @@ def test_exception_printing_skip(): assert s.startswith("Skipped") -def test_importorskip(monkeypatch): +def test_importorskip(monkeypatch) -> None: importorskip = pytest.importorskip def f(): @@ -704,45 +698,51 @@ def test_importorskip(monkeypatch): # path = pytest.importorskip("os.path") # assert path == os.path excinfo = pytest.raises(pytest.skip.Exception, f) - path = py.path.local(excinfo.getrepr().reprcrash.path) + assert excinfo is not None + excrepr = excinfo.getrepr() + assert excrepr is not None + assert excrepr.reprcrash is not None + path = py.path.local(excrepr.reprcrash.path) # check that importorskip reports the actual call # in this test the test_runner.py file assert path.purebasename == "test_runner" pytest.raises(SyntaxError, pytest.importorskip, "x y z") pytest.raises(SyntaxError, pytest.importorskip, "x=y") mod = types.ModuleType("hello123") - mod.__version__ = "1.3" + mod.__version__ = "1.3" # type: ignore monkeypatch.setitem(sys.modules, "hello123", mod) with pytest.raises(pytest.skip.Exception): pytest.importorskip("hello123", minversion="1.3.1") mod2 = pytest.importorskip("hello123", minversion="1.3") assert mod2 == mod - except pytest.skip.Exception: - print(_pytest._code.ExceptionInfo.from_current()) - pytest.fail("spurious skip") + except pytest.skip.Exception: # pragma: no cover + assert False, "spurious skip: {}".format( + _pytest._code.ExceptionInfo.from_current() + ) -def test_importorskip_imports_last_module_part(): +def test_importorskip_imports_last_module_part() -> None: ospath = pytest.importorskip("os.path") assert os.path == ospath -def test_importorskip_dev_module(monkeypatch): +def test_importorskip_dev_module(monkeypatch) -> None: try: mod = types.ModuleType("mockmodule") - mod.__version__ = "0.13.0.dev-43290" + mod.__version__ = "0.13.0.dev-43290" # type: ignore monkeypatch.setitem(sys.modules, "mockmodule", mod) mod2 = pytest.importorskip("mockmodule", minversion="0.12.0") assert mod2 == mod with pytest.raises(pytest.skip.Exception): pytest.importorskip("mockmodule1", minversion="0.14.0") - except pytest.skip.Exception: - print(_pytest._code.ExceptionInfo.from_current()) - pytest.fail("spurious skip") + except pytest.skip.Exception: # pragma: no cover + assert False, "spurious skip: {}".format( + _pytest._code.ExceptionInfo.from_current() + ) -def test_importorskip_module_level(testdir): - """importorskip must be able to skip entire modules when used at module level""" +def test_importorskip_module_level(testdir) -> None: + """`importorskip` must be able to skip entire modules when used at module level.""" testdir.makepyfile( """ import pytest @@ -756,8 +756,8 @@ def test_importorskip_module_level(testdir): result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) -def test_importorskip_custom_reason(testdir): - """make sure custom reasons are used""" +def test_importorskip_custom_reason(testdir) -> None: + """Make sure custom reasons are used.""" testdir.makepyfile( """ import pytest @@ -772,7 +772,7 @@ def test_importorskip_custom_reason(testdir): result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) -def test_pytest_cmdline_main(testdir): +def test_pytest_cmdline_main(testdir) -> None: p = testdir.makepyfile( """ import pytest @@ -790,18 +790,17 @@ def test_pytest_cmdline_main(testdir): assert ret == 0 -def test_unicode_in_longrepr(testdir): +def test_unicode_in_longrepr(testdir) -> None: testdir.makeconftest( - """ - # -*- coding: utf-8 -*- + """\ import pytest @pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(): outcome = yield rep = outcome.get_result() if rep.when == "call": - rep.longrepr = u'ä' - """ + rep.longrepr = 'ä' + """ ) testdir.makepyfile( """ @@ -814,7 +813,7 @@ def test_unicode_in_longrepr(testdir): assert "UnicodeEncodeError" not in result.stderr.str() -def test_failure_in_setup(testdir): +def test_failure_in_setup(testdir) -> None: testdir.makepyfile( """ def setup_module(): @@ -824,10 +823,10 @@ def test_failure_in_setup(testdir): """ ) result = testdir.runpytest("--tb=line") - assert "def setup_module" not in result.stdout.str() + result.stdout.no_fnmatch_line("*def setup_module*") -def test_makereport_getsource(testdir): +def test_makereport_getsource(testdir) -> None: testdir.makepyfile( """ def test_foo(): @@ -836,21 +835,21 @@ def test_makereport_getsource(testdir): """ ) result = testdir.runpytest() - assert "INTERNALERROR" not in result.stdout.str() + result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stdout.fnmatch_lines(["*else: assert False*"]) -def test_makereport_getsource_dynamic_code(testdir, monkeypatch): +def test_makereport_getsource_dynamic_code(testdir, monkeypatch) -> None: """Test that exception in dynamically generated code doesn't break getting the source line.""" import inspect original_findsource = inspect.findsource - def findsource(obj, *args, **kwargs): + def findsource(obj): # Can be triggered by dynamically created functions if obj.__name__ == "foo": raise IndexError() - return original_findsource(obj, *args, **kwargs) + return original_findsource(obj) monkeypatch.setattr(inspect, "findsource", findsource) @@ -867,16 +866,15 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch): """ ) result = testdir.runpytest("-vv") - assert "INTERNALERROR" not in result.stdout.str() + result.stdout.no_fnmatch_line("*INTERNALERROR*") result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"]) -def test_store_except_info_on_error(): - """ Test that upon test failure, the exception info is stored on - sys.last_traceback and friends. - """ +def test_store_except_info_on_error() -> None: + """Test that upon test failure, the exception info is stored on + sys.last_traceback and friends.""" # Simulate item that might raise a specific exception, depending on `raise_error` class var - class ItemMightRaise(object): + class ItemMightRaise: nodeid = "item_that_raises" raise_error = True @@ -885,24 +883,25 @@ def test_store_except_info_on_error(): raise IndexError("TEST") try: - runner.pytest_runtest_call(ItemMightRaise()) + runner.pytest_runtest_call(ItemMightRaise()) # type: ignore[arg-type] except IndexError: pass # Check that exception info is stored on sys assert sys.last_type is IndexError + assert isinstance(sys.last_value, IndexError) assert sys.last_value.args[0] == "TEST" assert sys.last_traceback # The next run should clear the exception info stored by the previous run ItemMightRaise.raise_error = False - runner.pytest_runtest_call(ItemMightRaise()) - assert sys.last_type is None - assert sys.last_value is None - assert sys.last_traceback is None + runner.pytest_runtest_call(ItemMightRaise()) # type: ignore[arg-type] + assert not hasattr(sys, "last_type") + assert not hasattr(sys, "last_value") + assert not hasattr(sys, "last_traceback") -def test_current_test_env_var(testdir, monkeypatch): - pytest_current_test_vars = [] +def test_current_test_env_var(testdir, monkeypatch) -> None: + pytest_current_test_vars = [] # type: List[Tuple[str, str]] monkeypatch.setattr( sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False ) @@ -933,15 +932,13 @@ def test_current_test_env_var(testdir, monkeypatch): assert "PYTEST_CURRENT_TEST" not in os.environ -class TestReportContents(object): - """ - Test user-level API of ``TestReport`` objects. - """ +class TestReportContents: + """Test user-level API of ``TestReport`` objects.""" def getrunner(self): return lambda item: runner.runtestprotocol(item, log=False) - def test_longreprtext_pass(self, testdir): + def test_longreprtext_pass(self, testdir) -> None: reports = testdir.runitem( """ def test_func(): @@ -951,7 +948,34 @@ class TestReportContents(object): rep = reports[1] assert rep.longreprtext == "" - def test_longreprtext_failure(self, testdir): + def test_longreprtext_skip(self, testdir) -> None: + """TestReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)""" + reports = testdir.runitem( + """ + import pytest + def test_func(): + pytest.skip() + """ + ) + _, call_rep, _ = reports + assert isinstance(call_rep.longrepr, tuple) + assert "Skipped" in call_rep.longreprtext + + def test_longreprtext_collect_skip(self, testdir) -> None: + """CollectReport.longreprtext can handle non-str ``longrepr`` attributes (#7559)""" + testdir.makepyfile( + """ + import pytest + pytest.skip(allow_module_level=True) + """ + ) + rec = testdir.inline_run() + calls = rec.getcalls("pytest_collectreport") + _, call = calls + assert isinstance(call.report.longrepr, tuple) + assert "Skipped" in call.report.longreprtext + + def test_longreprtext_failure(self, testdir) -> None: reports = testdir.runitem( """ def test_func(): @@ -962,7 +986,7 @@ class TestReportContents(object): rep = reports[1] assert "assert 1 == 4" in rep.longreprtext - def test_captured_text(self, testdir): + def test_captured_text(self, testdir) -> None: reports = testdir.runitem( """ import pytest @@ -992,7 +1016,7 @@ class TestReportContents(object): assert call.capstderr == "setup: stderr\ncall: stderr\n" assert teardown.capstderr == "setup: stderr\ncall: stderr\nteardown: stderr\n" - def test_no_captured_text(self, testdir): + def test_no_captured_text(self, testdir) -> None: reports = testdir.runitem( """ def test_func(): @@ -1002,3 +1026,29 @@ class TestReportContents(object): rep = reports[1] assert rep.capstdout == "" assert rep.capstderr == "" + + def test_longrepr_type(self, testdir) -> None: + reports = testdir.runitem( + """ + import pytest + def test_func(): + pytest.fail(pytrace=False) + """ + ) + rep = reports[1] + assert isinstance(rep.longrepr, _pytest._code.code.ExceptionRepr) + + +def test_outcome_exception_bad_msg() -> None: + """Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)""" + + def func() -> None: + raise NotImplementedError() + + expected = ( + "OutcomeException expected string as 'msg' parameter, got 'function' instead.\n" + "Perhaps you meant to use a mark?" + ) + with pytest.raises(TypeError) as excinfo: + OutcomeException(func) # type: ignore + assert str(excinfo.value) == expected diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py index 5be7297f6c5..1abb35043b7 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_runner_xunit.py @@ -1,11 +1,5 @@ -# -*- coding: utf-8 -*- -""" - test correct setup/teardowns at - module, class, and instance level -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function +"""Test correct setup/teardowns at module, class, and instance level.""" +from typing import List import pytest @@ -239,20 +233,20 @@ def test_setup_funcarg_setup_when_outer_scope_fails(testdir): "*ValueError*42*", "*function2*", "*ValueError*42*", - "*2 error*", + "*2 errors*", ] ) - assert "xyz43" not in result.stdout.str() + result.stdout.no_fnmatch_line("*xyz43*") @pytest.mark.parametrize("arg", ["", "arg"]) def test_setup_teardown_function_level_with_optional_argument( - testdir, monkeypatch, arg -): - """parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" + testdir, monkeypatch, arg: str, +) -> None: + """Parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" import sys - trace_setups_teardowns = [] + trace_setups_teardowns = [] # type: List[str] monkeypatch.setattr( sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_session.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_session.py index 9acad5deafd..1800771dad5 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_session.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_session.py @@ -1,13 +1,8 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import pytest -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.config import ExitCode -class SessionTests(object): +class SessionTests: def test_basic_testitem_events(self, testdir): tfile = testdir.makepyfile( """ @@ -76,7 +71,7 @@ class SessionTests(object): values = reprec.getfailedcollections() assert len(values) == 1 out = str(values[0].longrepr) - assert out.find(str("not python")) != -1 + assert out.find("not python") != -1 def test_exit_first_problem(self, testdir): reprec = testdir.inline_runsource( @@ -107,15 +102,20 @@ class SessionTests(object): p = testdir.makepyfile( """ import pytest + + class reprexc(BaseException): + def __str__(self): + return "Ha Ha fooled you, I'm a broken repr()." + class BrokenRepr1(object): foo=0 def __repr__(self): - raise Exception("Ha Ha fooled you, I'm a broken repr().") + raise reprexc class TestBrokenClass(object): def test_explicit_bad_repr(self): t = BrokenRepr1() - with pytest.raises(Exception, match="I'm a broken repr"): + with pytest.raises(BaseException, match="broken repr"): repr(t) def test_implicit_bad_repr1(self): @@ -128,12 +128,7 @@ class SessionTests(object): passed, skipped, failed = reprec.listoutcomes() assert (len(passed), len(skipped), len(failed)) == (1, 0, 1) out = failed[0].longrepr.reprcrash.message - assert ( - out.find( - """[Exception("Ha Ha fooled you, I'm a broken repr().") raised in repr()]""" - ) - != -1 - ) + assert out.find("<[reprexc() raised in repr()] BrokenRepr1") != -1 def test_broken_repr_with_showlocals_verbose(self, testdir): p = testdir.makepyfile( @@ -156,7 +151,7 @@ class SessionTests(object): assert repr_locals.lines assert len(repr_locals.lines) == 1 assert repr_locals.lines[0].startswith( - 'x = <[NotImplementedError("") raised in repr()] ObjWithErrorInRepr' + "x = <[NotImplementedError() raised in repr()] ObjWithErrorInRepr" ) def test_skip_file_by_conftest(self, testdir): @@ -335,7 +330,7 @@ def test_sessionfinish_with_start(testdir): """ ) res = testdir.runpytest("--collect-only") - assert res.ret == EXIT_NOTESTSCOLLECTED + assert res.ret == ExitCode.NO_TESTS_COLLECTED @pytest.mark.parametrize("path", ["root", "{relative}/root", "{environment}/root"]) @@ -364,14 +359,6 @@ def test_rootdir_option_arg(testdir, monkeypatch, path): def test_rootdir_wrong_option_arg(testdir): - testdir.makepyfile( - """ - import os - def test_one(): - assert 1 - """ - ) - result = testdir.runpytest("--rootdir=wrong_dir") result.stderr.fnmatch_lines( ["*Directory *wrong_dir* not found. Check your '--rootdir' option.*"] diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py new file mode 100644 index 00000000000..a43c850696e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_setuponly.py @@ -0,0 +1,315 @@ +import sys + +import pytest +from _pytest.config import ExitCode + + +@pytest.fixture(params=["--setup-only", "--setup-plan", "--setup-show"], scope="module") +def mode(request): + return request.param + + +def test_show_only_active_fixtures(testdir, mode, dummy_yaml_custom_test): + testdir.makepyfile( + ''' + import pytest + @pytest.fixture + def _arg0(): + """hidden arg0 fixture""" + @pytest.fixture + def arg1(): + """arg1 docstring""" + def test_arg1(arg1): + pass + ''' + ) + + result = testdir.runpytest(mode) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + ["*SETUP F arg1*", "*test_arg1 (fixtures used: arg1)*", "*TEARDOWN F arg1*"] + ) + result.stdout.no_fnmatch_line("*_arg0*") + + +def test_show_different_scopes(testdir, mode): + p = testdir.makepyfile( + ''' + import pytest + @pytest.fixture + def arg_function(): + """function scoped fixture""" + @pytest.fixture(scope='session') + def arg_session(): + """session scoped fixture""" + def test_arg1(arg_session, arg_function): + pass + ''' + ) + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + [ + "SETUP S arg_session*", + "*SETUP F arg_function*", + "*test_arg1 (fixtures used: arg_function, arg_session)*", + "*TEARDOWN F arg_function*", + "TEARDOWN S arg_session*", + ] + ) + + +def test_show_nested_fixtures(testdir, mode): + testdir.makeconftest( + ''' + import pytest + @pytest.fixture(scope='session') + def arg_same(): + """session scoped fixture""" + ''' + ) + p = testdir.makepyfile( + ''' + import pytest + @pytest.fixture(scope='function') + def arg_same(arg_same): + """function scoped fixture""" + def test_arg1(arg_same): + pass + ''' + ) + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + [ + "SETUP S arg_same*", + "*SETUP F arg_same (fixtures used: arg_same)*", + "*test_arg1 (fixtures used: arg_same)*", + "*TEARDOWN F arg_same*", + "TEARDOWN S arg_same*", + ] + ) + + +def test_show_fixtures_with_autouse(testdir, mode): + p = testdir.makepyfile( + ''' + import pytest + @pytest.fixture + def arg_function(): + """function scoped fixture""" + @pytest.fixture(scope='session', autouse=True) + def arg_session(): + """session scoped fixture""" + def test_arg1(arg_function): + pass + ''' + ) + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + [ + "SETUP S arg_session*", + "*SETUP F arg_function*", + "*test_arg1 (fixtures used: arg_function, arg_session)*", + ] + ) + + +def test_show_fixtures_with_parameters(testdir, mode): + testdir.makeconftest( + ''' + import pytest + @pytest.fixture(scope='session', params=['foo', 'bar']) + def arg_same(): + """session scoped fixture""" + ''' + ) + p = testdir.makepyfile( + ''' + import pytest + @pytest.fixture(scope='function') + def arg_other(arg_same): + """function scoped fixture""" + def test_arg1(arg_other): + pass + ''' + ) + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + [ + "SETUP S arg_same?'foo'?", + "TEARDOWN S arg_same?'foo'?", + "SETUP S arg_same?'bar'?", + "TEARDOWN S arg_same?'bar'?", + ] + ) + + +def test_show_fixtures_with_parameter_ids(testdir, mode): + testdir.makeconftest( + ''' + import pytest + @pytest.fixture( + scope='session', params=['foo', 'bar'], ids=['spam', 'ham']) + def arg_same(): + """session scoped fixture""" + ''' + ) + p = testdir.makepyfile( + ''' + import pytest + @pytest.fixture(scope='function') + def arg_other(arg_same): + """function scoped fixture""" + def test_arg1(arg_other): + pass + ''' + ) + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + ["SETUP S arg_same?'spam'?", "SETUP S arg_same?'ham'?"] + ) + + +def test_show_fixtures_with_parameter_ids_function(testdir, mode): + p = testdir.makepyfile( + """ + import pytest + @pytest.fixture(params=['foo', 'bar'], ids=lambda p: p.upper()) + def foobar(): + pass + def test_foobar(foobar): + pass + """ + ) + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + ["*SETUP F foobar?'FOO'?", "*SETUP F foobar?'BAR'?"] + ) + + +def test_dynamic_fixture_request(testdir): + p = testdir.makepyfile( + """ + import pytest + @pytest.fixture() + def dynamically_requested_fixture(): + pass + @pytest.fixture() + def dependent_fixture(request): + request.getfixturevalue('dynamically_requested_fixture') + def test_dyn(dependent_fixture): + pass + """ + ) + + result = testdir.runpytest("--setup-only", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines( + [ + "*SETUP F dynamically_requested_fixture", + "*TEARDOWN F dynamically_requested_fixture", + ] + ) + + +def test_capturing(testdir): + p = testdir.makepyfile( + """ + import pytest, sys + @pytest.fixture() + def one(): + sys.stdout.write('this should be captured') + sys.stderr.write('this should also be captured') + @pytest.fixture() + def two(one): + assert 0 + def test_capturing(two): + pass + """ + ) + + result = testdir.runpytest("--setup-only", p) + result.stdout.fnmatch_lines( + ["this should be captured", "this should also be captured"] + ) + + +def test_show_fixtures_and_execute_test(testdir): + """Verify that setups are shown and tests are executed.""" + p = testdir.makepyfile( + """ + import pytest + @pytest.fixture + def arg(): + assert True + def test_arg(arg): + assert False + """ + ) + + result = testdir.runpytest("--setup-show", p) + assert result.ret == 1 + + result.stdout.fnmatch_lines( + ["*SETUP F arg*", "*test_arg (fixtures used: arg)F*", "*TEARDOWN F arg*"] + ) + + +def test_setup_show_with_KeyboardInterrupt_in_test(testdir): + p = testdir.makepyfile( + """ + import pytest + @pytest.fixture + def arg(): + pass + def test_arg(arg): + raise KeyboardInterrupt() + """ + ) + result = testdir.runpytest("--setup-show", p, no_reraise_ctrlc=True) + result.stdout.fnmatch_lines( + [ + "*SETUP F arg*", + "*test_arg (fixtures used: arg)*", + "*TEARDOWN F arg*", + "*! KeyboardInterrupt !*", + "*= no tests ran in *", + ] + ) + assert result.ret == ExitCode.INTERRUPTED + + +def test_show_fixture_action_with_bytes(testdir): + # Issue 7126, BytesWarning when using --setup-show with bytes parameter + test_file = testdir.makepyfile( + """ + import pytest + + @pytest.mark.parametrize('data', [b'Hello World']) + def test_data(data): + pass + """ + ) + result = testdir.run( + sys.executable, "-bb", "-m", "pytest", "--setup-show", str(test_file) + ) + assert result.ret == 0 diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_setupplan.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_setupplan.py new file mode 100644 index 00000000000..929e883cce2 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_setupplan.py @@ -0,0 +1,111 @@ +def test_show_fixtures_and_test(testdir, dummy_yaml_custom_test): + """Verify that fixtures are not executed.""" + testdir.makepyfile( + """ + import pytest + @pytest.fixture + def arg(): + assert False + def test_arg(arg): + assert False + """ + ) + + result = testdir.runpytest("--setup-plan") + assert result.ret == 0 + + result.stdout.fnmatch_lines( + ["*SETUP F arg*", "*test_arg (fixtures used: arg)", "*TEARDOWN F arg*"] + ) + + +def test_show_multi_test_fixture_setup_and_teardown_correctly_simple(testdir): + """Verify that when a fixture lives for longer than a single test, --setup-plan + correctly displays the SETUP/TEARDOWN indicators the right number of times. + + As reported in https://github.com/pytest-dev/pytest/issues/2049 + --setup-plan was showing SETUP/TEARDOWN on every test, even when the fixture + should persist through multiple tests. + + (Note that this bug never affected actual test execution, which used the + correct fixture lifetimes. It was purely a display bug for --setup-plan, and + did not affect the related --setup-show or --setup-only.) + """ + testdir.makepyfile( + """ + import pytest + @pytest.fixture(scope = 'class') + def fix(): + return object() + class TestClass: + def test_one(self, fix): + assert False + def test_two(self, fix): + assert False + """ + ) + + result = testdir.runpytest("--setup-plan") + assert result.ret == 0 + + setup_fragment = "SETUP C fix" + setup_count = 0 + + teardown_fragment = "TEARDOWN C fix" + teardown_count = 0 + + for line in result.stdout.lines: + if setup_fragment in line: + setup_count += 1 + if teardown_fragment in line: + teardown_count += 1 + + # before the fix this tests, there would have been a setup/teardown + # message for each test, so the counts would each have been 2 + assert setup_count == 1 + assert teardown_count == 1 + + +def test_show_multi_test_fixture_setup_and_teardown_same_as_setup_show(testdir): + """Verify that SETUP/TEARDOWN messages match what comes out of --setup-show.""" + testdir.makepyfile( + """ + import pytest + @pytest.fixture(scope = 'session') + def sess(): + return True + @pytest.fixture(scope = 'module') + def mod(): + return True + @pytest.fixture(scope = 'class') + def cls(): + return True + @pytest.fixture(scope = 'function') + def func(): + return True + def test_outside(sess, mod, cls, func): + assert True + class TestCls: + def test_one(self, sess, mod, cls, func): + assert True + def test_two(self, sess, mod, cls, func): + assert True + """ + ) + + plan_result = testdir.runpytest("--setup-plan") + show_result = testdir.runpytest("--setup-show") + + # the number and text of these lines should be identical + plan_lines = [ + line + for line in plan_result.stdout.lines + if "SETUP" in line or "TEARDOWN" in line + ] + show_lines = [ + line + for line in show_result.stdout.lines + if "SETUP" in line or "TEARDOWN" in line + ] + + assert plan_lines == show_lines diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py index 7834971125a..b32d2267d21 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_skipping.py @@ -1,90 +1,76 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import sys import pytest +from _pytest.pytester import Testdir from _pytest.runner import runtestprotocol -from _pytest.skipping import MarkEvaluator +from _pytest.skipping import evaluate_skip_marks +from _pytest.skipping import evaluate_xfail_marks from _pytest.skipping import pytest_runtest_setup -class TestEvaluator(object): +class TestEvaluation: def test_no_marker(self, testdir): item = testdir.getitem("def test_func(): pass") - evalskipif = MarkEvaluator(item, "skipif") - assert not evalskipif - assert not evalskipif.istrue() + skipped = evaluate_skip_marks(item) + assert not skipped - def test_marked_no_args(self, testdir): + def test_marked_xfail_no_args(self, testdir): item = testdir.getitem( """ import pytest - @pytest.mark.xyz + @pytest.mark.xfail def test_func(): pass """ ) - ev = MarkEvaluator(item, "xyz") - assert ev - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "" - assert not ev.get("run", False) + xfailed = evaluate_xfail_marks(item) + assert xfailed + assert xfailed.reason == "" + assert xfailed.run - def test_marked_one_arg(self, testdir): + def test_marked_skipif_no_args(self, testdir): item = testdir.getitem( """ import pytest - @pytest.mark.xyz("hasattr(os, 'sep')") + @pytest.mark.skipif def test_func(): pass """ ) - ev = MarkEvaluator(item, "xyz") - assert ev - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "condition: hasattr(os, 'sep')" - - @pytest.mark.skipif("sys.version_info[0] >= 3") - def test_marked_one_arg_unicode(self, testdir): + skipped = evaluate_skip_marks(item) + assert skipped + assert skipped.reason == "" + + def test_marked_one_arg(self, testdir): item = testdir.getitem( """ import pytest - @pytest.mark.xyz(u"hasattr(os, 'sep')") + @pytest.mark.skipif("hasattr(os, 'sep')") def test_func(): pass """ ) - ev = MarkEvaluator(item, "xyz") - assert ev - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "condition: hasattr(os, 'sep')" + skipped = evaluate_skip_marks(item) + assert skipped + assert skipped.reason == "condition: hasattr(os, 'sep')" def test_marked_one_arg_with_reason(self, testdir): item = testdir.getitem( """ import pytest - @pytest.mark.xyz("hasattr(os, 'sep')", attr=2, reason="hello world") + @pytest.mark.skipif("hasattr(os, 'sep')", attr=2, reason="hello world") def test_func(): pass """ ) - ev = MarkEvaluator(item, "xyz") - assert ev - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "hello world" - assert ev.get("attr") == 2 + skipped = evaluate_skip_marks(item) + assert skipped + assert skipped.reason == "hello world" def test_marked_one_arg_twice(self, testdir): lines = [ """@pytest.mark.skipif("not hasattr(os, 'murks')")""", - """@pytest.mark.skipif("hasattr(os, 'murks')")""", + """@pytest.mark.skipif(condition="hasattr(os, 'murks')")""", ] for i in range(0, 2): item = testdir.getitem( @@ -97,11 +83,9 @@ class TestEvaluator(object): """ % (lines[i], lines[(i + 1) % 2]) ) - ev = MarkEvaluator(item, "skipif") - assert ev - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "condition: not hasattr(os, 'murks')" + skipped = evaluate_skip_marks(item) + assert skipped + assert skipped.reason == "condition: not hasattr(os, 'murks')" def test_marked_one_arg_twice2(self, testdir): item = testdir.getitem( @@ -113,13 +97,11 @@ class TestEvaluator(object): pass """ ) - ev = MarkEvaluator(item, "skipif") - assert ev - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "condition: not hasattr(os, 'murks')" + skipped = evaluate_skip_marks(item) + assert skipped + assert skipped.reason == "condition: not hasattr(os, 'murks')" - def test_marked_skip_with_not_string(self, testdir): + def test_marked_skipif_with_boolean_without_reason(self, testdir) -> None: item = testdir.getitem( """ import pytest @@ -128,13 +110,34 @@ class TestEvaluator(object): pass """ ) - ev = MarkEvaluator(item, "skipif") - exc = pytest.raises(pytest.fail.Exception, ev.istrue) + with pytest.raises(pytest.fail.Exception) as excinfo: + evaluate_skip_marks(item) + assert excinfo.value.msg is not None assert ( - """Failed: you need to specify reason=STRING when using booleans as conditions.""" - in exc.value.msg + """Error evaluating 'skipif': you need to specify reason=STRING when using booleans as conditions.""" + in excinfo.value.msg ) + def test_marked_skipif_with_invalid_boolean(self, testdir) -> None: + item = testdir.getitem( + """ + import pytest + + class InvalidBool: + def __bool__(self): + raise TypeError("INVALID") + + @pytest.mark.skipif(InvalidBool(), reason="xxx") + def test_func(): + pass + """ + ) + with pytest.raises(pytest.fail.Exception) as excinfo: + evaluate_skip_marks(item) + assert excinfo.value.msg is not None + assert "Error evaluating 'skipif' condition as a boolean" in excinfo.value.msg + assert "INVALID" in excinfo.value.msg + def test_skipif_class(self, testdir): (item,) = testdir.getitems( """ @@ -146,13 +149,12 @@ class TestEvaluator(object): """ ) item.config._hackxyz = 3 - ev = MarkEvaluator(item, "skipif") - assert ev.istrue() - expl = ev.getexplanation() - assert expl == "condition: config._hackxyz" + skipped = evaluate_skip_marks(item) + assert skipped + assert skipped.reason == "condition: config._hackxyz" -class TestXFail(object): +class TestXFail: @pytest.mark.parametrize("strict", [True, False]) def test_xfail_simple(self, testdir, strict): item = testdir.getitem( @@ -186,9 +188,7 @@ class TestXFail(object): assert callreport.wasxfail == "this is an xfail" def test_xfail_using_platform(self, testdir): - """ - Verify that platform can be used with xfail statements. - """ + """Verify that platform can be used with xfail statements.""" item = testdir.getitem( """ import pytest @@ -215,7 +215,7 @@ class TestXFail(object): assert len(reports) == 3 callreport = reports[1] assert callreport.failed - assert callreport.longrepr == "[XPASS(strict)] nope" + assert str(callreport.longrepr) == "[XPASS(strict)] nope" assert not hasattr(callreport, "wasxfail") def test_xfail_run_anyway(self, testdir): @@ -234,6 +234,31 @@ class TestXFail(object): ["*def test_func():*", "*assert 0*", "*1 failed*1 pass*"] ) + @pytest.mark.parametrize( + "test_input,expected", + [ + ( + ["-rs"], + ["SKIPPED [1] test_sample.py:2: unconditional skip", "*1 skipped*"], + ), + ( + ["-rs", "--runxfail"], + ["SKIPPED [1] test_sample.py:2: unconditional skip", "*1 skipped*"], + ), + ], + ) + def test_xfail_run_with_skip_mark(self, testdir, test_input, expected): + testdir.makepyfile( + test_sample=""" + import pytest + @pytest.mark.skip + def test_skip_location() -> None: + assert 0 + """ + ) + result = testdir.runpytest(*test_input) + result.stdout.fnmatch_lines(expected) + def test_xfail_evalfalse_but_fails(self, testdir): item = testdir.getitem( """ @@ -399,6 +424,33 @@ class TestXFail(object): result = testdir.runpytest(p) result.stdout.fnmatch_lines(["*1 xfailed*"]) + def test_dynamic_xfail_set_during_runtest_failed(self, testdir: Testdir) -> None: + # Issue #7486. + p = testdir.makepyfile( + """ + import pytest + def test_this(request): + request.node.add_marker(pytest.mark.xfail(reason="xfail")) + assert 0 + """ + ) + result = testdir.runpytest(p) + result.assert_outcomes(xfailed=1) + + def test_dynamic_xfail_set_during_runtest_passed_strict( + self, testdir: Testdir + ) -> None: + # Issue #7486. + p = testdir.makepyfile( + """ + import pytest + def test_this(request): + request.node.add_marker(pytest.mark.xfail(reason="xfail", strict=True)) + """ + ) + result = testdir.runpytest(p) + result.assert_outcomes(failed=1) + @pytest.mark.parametrize( "expected, actual, matchline", [ @@ -422,9 +474,8 @@ class TestXFail(object): result.stdout.fnmatch_lines([matchline]) def test_strict_sanity(self, testdir): - """sanity check for xfail(strict=True): a failing test should behave - exactly like a normal xfail. - """ + """Sanity check for xfail(strict=True): a failing test should behave + exactly like a normal xfail.""" p = testdir.makepyfile( """ import pytest @@ -519,7 +570,7 @@ class TestXFail(object): assert result.ret == (1 if strict else 0) -class TestXFailwithSetupTeardown(object): +class TestXFailwithSetupTeardown: def test_failing_setup_issue9(self, testdir): testdir.makepyfile( """ @@ -551,7 +602,7 @@ class TestXFailwithSetupTeardown(object): result.stdout.fnmatch_lines(["*1 xfail*"]) -class TestSkip(object): +class TestSkip: def test_skip_class(self, testdir): testdir.makepyfile( """ @@ -648,7 +699,7 @@ class TestSkip(object): result.stdout.fnmatch_lines(["*unconditional skip*", "*1 skipped*"]) -class TestSkipif(object): +class TestSkipif: def test_skipif_conditional(self, testdir): item = testdir.getitem( """ @@ -752,23 +803,37 @@ def test_skipif_class(testdir): def test_skipped_reasons_functional(testdir): testdir.makepyfile( test_one=""" + import pytest from conftest import doskip + def setup_function(func): doskip() + def test_func(): pass + class TestClass(object): def test_method(self): doskip() - """, + + @pytest.mark.skip("via_decorator") + def test_deco(self): + assert 0 + """, conftest=""" - import pytest + import pytest, sys def doskip(): + assert sys._getframe().f_lineno == 3 pytest.skip('test') """, ) result = testdir.runpytest("-rs") - result.stdout.fnmatch_lines(["*SKIP*2*conftest.py:4: test"]) + result.stdout.fnmatch_lines_random( + [ + "SKIPPED [[]2[]] conftest.py:4: test", + "SKIPPED [[]1[]] test_one.py:14: via_decorator", + ] + ) assert result.ret == 0 @@ -876,7 +941,7 @@ def test_reportchars_all_error(testdir): result.stdout.fnmatch_lines(["ERROR*test_foo*"]) -def test_errors_in_xfail_skip_expressions(testdir): +def test_errors_in_xfail_skip_expressions(testdir) -> None: testdir.makepyfile( """ import pytest @@ -893,24 +958,22 @@ def test_errors_in_xfail_skip_expressions(testdir): ) result = testdir.runpytest() markline = " ^" - if sys.platform.startswith("java"): - # XXX report this to java - markline = "*" + markline[8:] - elif hasattr(sys, "pypy_version_info") and sys.pypy_version_info < (6,): + pypy_version_info = getattr(sys, "pypy_version_info", None) + if pypy_version_info is not None and pypy_version_info < (6,): markline = markline[5:] elif sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"): markline = markline[4:] result.stdout.fnmatch_lines( [ "*ERROR*test_nameerror*", - "*evaluating*skipif*expression*", + "*evaluating*skipif*condition*", "*asd*", "*ERROR*test_syntax*", - "*evaluating*xfail*expression*", + "*evaluating*xfail*condition*", " syntax error", markline, "SyntaxError: invalid syntax", - "*1 pass*2 error*", + "*1 pass*2 errors*", ] ) @@ -932,25 +995,12 @@ def test_xfail_skipif_with_globals(testdir): result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*", "*x == 3*"]) -def test_direct_gives_error(testdir): - testdir.makepyfile( - """ - import pytest - @pytest.mark.skipif(True) - def test_skip1(): - pass - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines(["*1 error*"]) - - def test_default_markers(testdir): result = testdir.runpytest("--markers") result.stdout.fnmatch_lines( [ - "*skipif(*condition)*skip*", - "*xfail(*condition, reason=None, run=True, raises=None, strict=False)*expected failure*", + "*skipif(condition, ..., [*], reason=...)*skip*", + "*xfail(condition, ..., [*], reason=..., run=True, raises=None, strict=xfail_strict)*expected failure*", ] ) @@ -973,7 +1023,7 @@ def test_xfail_test_setup_exception(testdir): result = testdir.runpytest(p) assert result.ret == 0 assert "xfailed" in result.stdout.str() - assert "xpassed" not in result.stdout.str() + result.stdout.no_fnmatch_line("*xpassed*") def test_imperativeskip_on_xfail_test(testdir): @@ -1006,7 +1056,7 @@ def test_imperativeskip_on_xfail_test(testdir): ) -class TestBooleanCondition(object): +class TestBooleanCondition: def test_skipif(self, testdir): testdir.makepyfile( """ @@ -1073,7 +1123,7 @@ def test_xfail_item(testdir): pytest.xfail("Expected Failure") def pytest_collect_file(path, parent): - return MyItem("foo", parent) + return MyItem.from_parent(name="foo", parent=parent) """ ) result = testdir.inline_run() @@ -1084,13 +1134,12 @@ def test_xfail_item(testdir): def test_module_level_skip_error(testdir): - """ - Verify that using pytest.skip at module level causes a collection error - """ + """Verify that using pytest.skip at module level causes a collection error.""" testdir.makepyfile( """ import pytest - @pytest.skip + pytest.skip("skip_module_level") + def test_func(): assert True """ @@ -1102,9 +1151,7 @@ def test_module_level_skip_error(testdir): def test_module_level_skip_with_allow_module_level(testdir): - """ - Verify that using pytest.skip(allow_module_level=True) is allowed - """ + """Verify that using pytest.skip(allow_module_level=True) is allowed.""" testdir.makepyfile( """ import pytest @@ -1119,9 +1166,7 @@ def test_module_level_skip_with_allow_module_level(testdir): def test_invalid_skip_keyword_parameter(testdir): - """ - Verify that using pytest.skip() with unknown parameter raises an error - """ + """Verify that using pytest.skip() with unknown parameter raises an error.""" testdir.makepyfile( """ import pytest @@ -1144,13 +1189,15 @@ def test_mark_xfail_item(testdir): class MyItem(pytest.Item): nodeid = 'foo' def setup(self): - marker = pytest.mark.xfail(True, reason="Expected failure") + marker = pytest.mark.xfail("1 == 2", reason="Expected failure - false") + self.add_marker(marker) + marker = pytest.mark.xfail(True, reason="Expected failure - true") self.add_marker(marker) def runtest(self): assert False def pytest_collect_file(path, parent): - return MyItem("foo", parent) + return MyItem.from_parent(name="foo", parent=parent) """ ) result = testdir.inline_run() @@ -1185,3 +1232,20 @@ def test_importorskip(): match="^could not import 'doesnotexist': No module named .*", ): pytest.importorskip("doesnotexist") + + +def test_relpath_rootdir(testdir): + testdir.makepyfile( + **{ + "tests/test_1.py": """ + import pytest + @pytest.mark.skip() + def test_pass(): + pass + """, + } + ) + result = testdir.runpytest("-rs", "tests/test_1.py", "--rootdir=tests") + result.stdout.fnmatch_lines( + ["SKIPPED [[]1[]] tests/test_1.py:2: unconditional skip"] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py index d8d11917ade..df66d798bbe 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_stepwise.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- -import sys - import pytest @pytest.fixture def stepwise_testdir(testdir): # Rather than having to modify our testfile between tests, we introduce - # a flag for wether or not the second test should fail. + # a flag for whether or not the second test should fail. testdir.makeconftest( """ def pytest_addoption(parser): @@ -78,6 +75,16 @@ def broken_testdir(testdir): return testdir +def _strip_resource_warnings(lines): + # Strip unreliable ResourceWarnings, so no-output assertions on stderr can work. + # (https://github.com/pytest-dev/pytest/issues/5088) + return [ + x + for x in lines + if not x.startswith(("Exception ignored in:", "ResourceWarning")) + ] + + def test_run_without_stepwise(stepwise_testdir): result = stepwise_testdir.runpytest("-v", "--strict-markers", "--fail") @@ -91,7 +98,7 @@ def test_fail_and_continue_with_stepwise(stepwise_testdir): result = stepwise_testdir.runpytest( "-v", "--strict-markers", "--stepwise", "--fail" ) - assert not result.stderr.str() + assert _strip_resource_warnings(result.stderr.lines) == [] stdout = result.stdout.str() # Make sure we stop after first failing test. @@ -101,7 +108,7 @@ def test_fail_and_continue_with_stepwise(stepwise_testdir): # "Fix" the test that failed in the last run and run it again. result = stepwise_testdir.runpytest("-v", "--strict-markers", "--stepwise") - assert not result.stderr.str() + assert _strip_resource_warnings(result.stderr.lines) == [] stdout = result.stdout.str() # Make sure the latest failing test runs and then continues. @@ -119,7 +126,7 @@ def test_run_with_skip_option(stepwise_testdir): "--fail", "--fail-last", ) - assert not result.stderr.str() + assert _strip_resource_warnings(result.stderr.lines) == [] stdout = result.stdout.str() # Make sure first fail is ignore and second fail stops the test run. @@ -132,7 +139,7 @@ def test_run_with_skip_option(stepwise_testdir): def test_fail_on_errors(error_testdir): result = error_testdir.runpytest("-v", "--strict-markers", "--stepwise") - assert not result.stderr.str() + assert _strip_resource_warnings(result.stderr.lines) == [] stdout = result.stdout.str() assert "test_error ERROR" in stdout @@ -143,7 +150,7 @@ def test_change_testfile(stepwise_testdir): result = stepwise_testdir.runpytest( "-v", "--strict-markers", "--stepwise", "--fail", "test_a.py" ) - assert not result.stderr.str() + assert _strip_resource_warnings(result.stderr.lines) == [] stdout = result.stdout.str() assert "test_fail_on_flag FAILED" in stdout @@ -153,7 +160,7 @@ def test_change_testfile(stepwise_testdir): result = stepwise_testdir.runpytest( "-v", "--strict-markers", "--stepwise", "test_b.py" ) - assert not result.stderr.str() + assert _strip_resource_warnings(result.stderr.lines) == [] stdout = result.stdout.str() assert "test_success PASSED" in stdout @@ -167,14 +174,16 @@ def test_stop_on_collection_errors(broken_testdir, broken_first): if broken_first: files.reverse() result = broken_testdir.runpytest("-v", "--strict-markers", "--stepwise", *files) - result.stdout.fnmatch_lines("*errors during collection*") + result.stdout.fnmatch_lines("*error during collection*") -def test_xfail_handling(testdir): +def test_xfail_handling(testdir, monkeypatch): """Ensure normal xfail is ignored, and strict xfail interrupts the session in sw mode (#5547) """ + monkeypatch.setattr("sys.dont_write_bytecode", True) + contents = """ import pytest def test_a(): pass @@ -208,10 +217,6 @@ def test_xfail_handling(testdir): ] ) - # because we are writing to the same file, mtime might not be affected enough to - # invalidate the cache, making this next run flaky - if not sys.dont_write_bytecode: - testdir.tmpdir.join("__pycache__").remove() testdir.makepyfile(contents.format(assert_value="0", strict="True")) result = testdir.runpytest("--sw", "-v") result.stdout.fnmatch_lines( diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_store.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_store.py new file mode 100644 index 00000000000..b6d4208a092 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_store.py @@ -0,0 +1,60 @@ +import pytest +from _pytest.store import Store +from _pytest.store import StoreKey + + +def test_store() -> None: + store = Store() + + key1 = StoreKey[str]() + key2 = StoreKey[int]() + + # Basic functionality - single key. + assert key1 not in store + store[key1] = "hello" + assert key1 in store + assert store[key1] == "hello" + assert store.get(key1, None) == "hello" + store[key1] = "world" + assert store[key1] == "world" + # Has correct type (no mypy error). + store[key1] + "string" + + # No interaction with another key. + assert key2 not in store + assert store.get(key2, None) is None + with pytest.raises(KeyError): + store[key2] + with pytest.raises(KeyError): + del store[key2] + store[key2] = 1 + assert store[key2] == 1 + # Has correct type (no mypy error). + store[key2] + 20 + del store[key1] + with pytest.raises(KeyError): + del store[key1] + with pytest.raises(KeyError): + store[key1] + + # setdefault + store[key1] = "existing" + assert store.setdefault(key1, "default") == "existing" + assert store[key1] == "existing" + key_setdefault = StoreKey[bytes]() + assert store.setdefault(key_setdefault, b"default") == b"default" + assert store[key_setdefault] == b"default" + + # Can't accidentally add attributes to store object itself. + with pytest.raises(AttributeError): + store.foo = "nope" # type: ignore[attr-defined] + + # No interaction with anoter store. + store2 = Store() + key3 = StoreKey[int]() + assert key2 not in store2 + store2[key2] = 100 + store2[key3] = 200 + assert store2[key2] + store2[key3] == 300 + assert store[key2] == 1 + assert key3 not in store diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py index 752c894ca93..51fdf728ec9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_terminal.py @@ -1,54 +1,54 @@ -# -*- coding: utf-8 -*- -""" -terminal reporting of the full testing process. -""" -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - +"""Terminal reporting of the full testing process.""" import collections import os import sys import textwrap +from io import StringIO +from typing import cast +from typing import Dict +from typing import List +from typing import Tuple import pluggy import py +import _pytest.config +import _pytest.terminal import pytest -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest._io.wcwidth import wcswidth +from _pytest.config import Config +from _pytest.config import ExitCode +from _pytest.monkeypatch import MonkeyPatch +from _pytest.pathlib import Path +from _pytest.pytester import Testdir from _pytest.reports import BaseReport +from _pytest.reports import CollectReport from _pytest.terminal import _folded_skips from _pytest.terminal import _get_line_with_reprcrash_message from _pytest.terminal import _plugin_nameversions -from _pytest.terminal import build_summary_stats_line from _pytest.terminal import getreportopt from _pytest.terminal import TerminalReporter DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"]) -class Option(object): - def __init__(self, verbosity=0, fulltrace=False): +TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"}) + + +class Option: + def __init__(self, verbosity=0): self.verbosity = verbosity - self.fulltrace = fulltrace @property def args(self): values = [] values.append("--verbosity=%d" % self.verbosity) - if self.fulltrace: - values.append("--fulltrace") return values @pytest.fixture( - params=[ - Option(verbosity=0), - Option(verbosity=1), - Option(verbosity=-1), - Option(fulltrace=True), - ], - ids=["default", "verbose", "quiet", "fulltrace"], + params=[Option(verbosity=0), Option(verbosity=1), Option(verbosity=-1)], + ids=["default", "verbose", "quiet"], ) def option(request): return request.param @@ -75,7 +75,7 @@ def test_plugin_nameversion(input, expected): assert result == expected -class TestTerminal(object): +class TestTerminal: def test_pass_skip_fail(self, testdir, option): testdir.makepyfile( """ @@ -162,6 +162,8 @@ class TestTerminal(object): "test2.py": "def test_2(): pass", } ) + # Explicitly test colored output. + testdir.monkeypatch.setenv("PY_COLORS", "1") child = testdir.spawn_pytest("-v test1.py test2.py") child.expect(r"collecting \.\.\.") @@ -169,30 +171,57 @@ class TestTerminal(object): child.expect(r"collecting 2 items") child.expect(r"collected 2 items") rest = child.read().decode("utf8") - assert "2 passed in" in rest + assert "= \x1b[32m\x1b[1m2 passed\x1b[0m\x1b[32m in" in rest def test_itemreport_subclasses_show_subclassed_file(self, testdir): testdir.makepyfile( - test_p1=""" + **{ + "tests/test_p1": """ class BaseTests(object): + fail = False + def test_p1(self): - pass - class TestClass(BaseTests): - pass - """ - ) - p2 = testdir.makepyfile( - test_p2=""" + if self.fail: assert 0 + """, + "tests/test_p2": """ from test_p1 import BaseTests - class TestMore(BaseTests): - pass - """ + + class TestMore(BaseTests): pass + """, + "tests/test_p3.py": """ + from test_p1 import BaseTests + + BaseTests.fail = True + + class TestMore(BaseTests): pass + """, + } + ) + result = testdir.runpytest("tests/test_p2.py", "--rootdir=tests") + result.stdout.fnmatch_lines(["tests/test_p2.py .*", "=* 1 passed in *"]) + + result = testdir.runpytest("-vv", "-rA", "tests/test_p2.py", "--rootdir=tests") + result.stdout.fnmatch_lines( + [ + "tests/test_p2.py::TestMore::test_p1 <- test_p1.py PASSED *", + "*= short test summary info =*", + "PASSED tests/test_p2.py::TestMore::test_p1", + ] ) - result = testdir.runpytest(p2) - result.stdout.fnmatch_lines(["*test_p2.py .*", "*1 passed*"]) - result = testdir.runpytest("-vv", p2) + result = testdir.runpytest("-vv", "-rA", "tests/test_p3.py", "--rootdir=tests") result.stdout.fnmatch_lines( - ["*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED*"] + [ + "tests/test_p3.py::TestMore::test_p1 <- test_p1.py FAILED *", + "*_ TestMore.test_p1 _*", + " def test_p1(self):", + "> if self.fail: assert 0", + "E assert 0", + "", + "tests/test_p1.py:5: AssertionError", + "*= short test summary info =*", + "FAILED tests/test_p3.py::TestMore::test_p1 - assert 0", + "*= 1 failed in *", + ] ) def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir): @@ -209,9 +238,10 @@ class TestTerminal(object): result = testdir.runpytest("-vv") assert result.ret == 0 result.stdout.fnmatch_lines(["*a123/test_hello123.py*PASS*"]) - assert " <- " not in result.stdout.str() + result.stdout.no_fnmatch_line("* <- *") - def test_keyboard_interrupt(self, testdir, option): + @pytest.mark.parametrize("fulltrace", ("", "--fulltrace")) + def test_keyboard_interrupt(self, testdir, fulltrace): testdir.makepyfile( """ def test_foobar(): @@ -223,7 +253,7 @@ class TestTerminal(object): """ ) - result = testdir.runpytest(*option.args, no_reraise_ctrlc=True) + result = testdir.runpytest(fulltrace, no_reraise_ctrlc=True) result.stdout.fnmatch_lines( [ " def test_foobar():", @@ -232,13 +262,13 @@ class TestTerminal(object): "*_keyboard_interrupt.py:6: KeyboardInterrupt*", ] ) - if option.fulltrace: + if fulltrace: result.stdout.fnmatch_lines( ["*raise KeyboardInterrupt # simulating the user*"] ) else: result.stdout.fnmatch_lines( - ["(to show a full traceback on KeyboardInterrupt use --fulltrace)"] + ["(to show a full traceback on KeyboardInterrupt use --full-trace)"] ) result.stdout.fnmatch_lines(["*KeyboardInterrupt*"]) @@ -273,7 +303,7 @@ class TestTerminal(object): def test_rewrite(self, testdir, monkeypatch): config = testdir.parseconfig() - f = py.io.TextIO() + f = StringIO() monkeypatch.setattr(f, "isatty", lambda *args: True) tr = TerminalReporter(config, f) tr._tw.fullwidth = 10 @@ -281,8 +311,31 @@ class TestTerminal(object): tr.rewrite("hey", erase=True) assert f.getvalue() == "hello" + "\r" + "hey" + (6 * " ") + def test_report_teststatus_explicit_markup( + self, testdir: Testdir, color_mapping + ) -> None: + """Test that TerminalReporter handles markup explicitly provided by + a pytest_report_teststatus hook.""" + testdir.monkeypatch.setenv("PY_COLORS", "1") + testdir.makeconftest( + """ + def pytest_report_teststatus(report): + return 'foo', 'F', ('FOO', {'red': True}) + """ + ) + testdir.makepyfile( + """ + def test_foobar(): + pass + """ + ) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines( + color_mapping.format_for_fnmatch(["*{red}FOO{reset}*"]) + ) + -class TestCollectonly(object): +class TestCollectonly: def test_collectonly_basic(self, testdir): testdir.makepyfile( """ @@ -305,17 +358,33 @@ class TestCollectonly(object): result = testdir.runpytest("--collect-only", "-rs") result.stdout.fnmatch_lines(["*ERROR collecting*"]) - def test_collectonly_display_test_description(self, testdir): + def test_collectonly_displays_test_description( + self, testdir: Testdir, dummy_yaml_custom_test + ) -> None: + """Used dummy_yaml_custom_test for an Item without ``obj``.""" testdir.makepyfile( """ def test_with_description(): - \""" This test has a description. - \""" - assert True - """ + ''' This test has a description. + + more1. + more2.''' + """ ) result = testdir.runpytest("--collect-only", "--verbose") - result.stdout.fnmatch_lines([" This test has a description."]) + result.stdout.fnmatch_lines( + [ + "<YamlFile test1.yaml>", + " <YamlItem test1.yaml>", + "<Module test_collectonly_displays_test_description.py>", + " <Function test_with_description>", + " This test has a description.", + " ", + " more1.", + " more2.", + ], + consecutive=True, + ) def test_collectonly_failed_module(self, testdir): testdir.makepyfile("""raise ValueError(0)""") @@ -371,13 +440,13 @@ class TestCollectonly(object): ) def test_collectonly_missing_path(self, testdir): - """this checks issue 115, - failure in parseargs will cause session - not to have the items attribute - """ + """Issue 115: failure in parseargs will cause session not to + have the items attribute.""" result = testdir.runpytest("--collect-only", "uhm_missing_path") assert result.ret == 4 - result.stderr.fnmatch_lines(["*ERROR: file not found*"]) + result.stderr.fnmatch_lines( + ["*ERROR: file or directory not found: uhm_missing_path"] + ) def test_collectonly_quiet(self, testdir): testdir.makepyfile("def test_foo(): pass") @@ -390,7 +459,7 @@ class TestCollectonly(object): result.stdout.fnmatch_lines(["*test_fun.py: 1*"]) -class TestFixtureReporting(object): +class TestFixtureReporting: def test_setup_fixture_error(self, testdir): testdir.makepyfile( """ @@ -462,7 +531,7 @@ class TestFixtureReporting(object): ) def test_setup_teardown_output_and_test_failure(self, testdir): - """ Test for issue #442 """ + """Test for issue #442.""" testdir.makepyfile( """ def setup_function(function): @@ -490,7 +559,7 @@ class TestFixtureReporting(object): ) -class TestTerminalFunctional(object): +class TestTerminalFunctional: def test_deselected(self, testdir): testpath = testdir.makepyfile( """ @@ -564,7 +633,7 @@ class TestTerminalFunctional(object): "*= 2 passed, 1 deselected in * =*", ] ) - assert "= 1 deselected =" not in result.stdout.str() + result.stdout.no_fnmatch_line("*= 1 deselected =*") assert result.ret == 0 def test_no_skip_summary_if_failure(self, testdir): @@ -602,6 +671,7 @@ class TestTerminalFunctional(object): assert result.ret == 0 def test_header_trailer_info(self, testdir, request): + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") testdir.makepyfile( """ def test_passes(): @@ -622,13 +692,36 @@ class TestTerminalFunctional(object): pluggy.__version__, ), "*test_header_trailer_info.py .*", - "=* 1 passed*in *.[0-9][0-9] seconds *=", + "=* 1 passed*in *.[0-9][0-9]s *=", ] ) if request.config.pluginmanager.list_plugin_distinfo(): result.stdout.fnmatch_lines(["plugins: *"]) - def test_header(self, testdir, request): + def test_no_header_trailer_info(self, testdir, request): + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") + testdir.makepyfile( + """ + def test_passes(): + pass + """ + ) + result = testdir.runpytest("--no-header") + verinfo = ".".join(map(str, sys.version_info[:3])) + result.stdout.no_fnmatch_line( + "platform %s -- Python %s*pytest-%s*py-%s*pluggy-%s" + % ( + sys.platform, + verinfo, + pytest.__version__, + py.__version__, + pluggy.__version__, + ) + ) + if request.config.pluginmanager.list_plugin_distinfo(): + result.stdout.no_fnmatch_line("plugins: *") + + def test_header(self, testdir): testdir.tmpdir.join("tests").ensure_dir() testdir.tmpdir.join("gui").ensure_dir() @@ -636,10 +729,10 @@ class TestTerminalFunctional(object): result = testdir.runpytest() result.stdout.fnmatch_lines(["rootdir: *test_header0"]) - # with inifile + # with configfile testdir.makeini("""[pytest]""") result = testdir.runpytest() - result.stdout.fnmatch_lines(["rootdir: *test_header0, inifile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) # with testpaths option, and not passing anything in the command-line testdir.makeini( @@ -650,12 +743,65 @@ class TestTerminalFunctional(object): ) result = testdir.runpytest() result.stdout.fnmatch_lines( - ["rootdir: *test_header0, inifile: tox.ini, testpaths: tests, gui"] + ["rootdir: *test_header0, configfile: tox.ini, testpaths: tests, gui"] ) # with testpaths option, passing directory in command-line: do not show testpaths then result = testdir.runpytest("tests") - result.stdout.fnmatch_lines(["rootdir: *test_header0, inifile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) + + def test_header_absolute_testpath( + self, testdir: Testdir, monkeypatch: MonkeyPatch + ) -> None: + """Regresstion test for #7814.""" + tests = testdir.tmpdir.join("tests") + tests.ensure_dir() + testdir.makepyprojecttoml( + """ + [tool.pytest.ini_options] + testpaths = ['{}'] + """.format( + tests + ) + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "rootdir: *absolute_testpath0, configfile: pyproject.toml, testpaths: {}".format( + tests + ) + ] + ) + + def test_no_header(self, testdir): + testdir.tmpdir.join("tests").ensure_dir() + testdir.tmpdir.join("gui").ensure_dir() + + # with testpaths option, and not passing anything in the command-line + testdir.makeini( + """ + [pytest] + testpaths = tests gui + """ + ) + result = testdir.runpytest("--no-header") + result.stdout.no_fnmatch_line( + "rootdir: *test_header0, inifile: tox.ini, testpaths: tests, gui" + ) + + # with testpaths option, passing directory in command-line: do not show testpaths then + result = testdir.runpytest("tests", "--no-header") + result.stdout.no_fnmatch_line("rootdir: *test_header0, inifile: tox.ini") + + def test_no_summary(self, testdir): + p1 = testdir.makepyfile( + """ + def test_no_summary(): + assert false + """ + ) + result = testdir.runpytest(p1, "--no-summary") + result.stdout.no_fnmatch_line("*= FAILURES =*") def test_showlocals(self, testdir): p1 = testdir.makepyfile( @@ -675,6 +821,26 @@ class TestTerminalFunctional(object): ] ) + def test_showlocals_short(self, testdir): + p1 = testdir.makepyfile( + """ + def test_showlocals_short(): + x = 3 + y = "xxxx" + assert 0 + """ + ) + result = testdir.runpytest(p1, "-l", "--tb=short") + result.stdout.fnmatch_lines( + [ + "test_showlocals_short.py:*", + " assert 0", + "E assert 0", + " x = 3", + " y = 'xxxx'", + ] + ) + @pytest.fixture def verbose_testfile(self, testdir): return testdir.makepyfile( @@ -694,7 +860,7 @@ class TestTerminalFunctional(object): """ ) - def test_verbose_reporting(self, verbose_testfile, testdir, pytestconfig): + def test_verbose_reporting(self, verbose_testfile, testdir): result = testdir.runpytest( verbose_testfile, "-v", "-Walways::pytest.PytestWarning" ) @@ -712,6 +878,7 @@ class TestTerminalFunctional(object): if not pytestconfig.pluginmanager.get_plugin("xdist"): pytest.skip("xdist plugin not installed") + testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD") result = testdir.runpytest( verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning" ) @@ -792,9 +959,9 @@ class TestTerminalFunctional(object): def test_fail_extra_reporting(testdir, monkeypatch): monkeypatch.setenv("COLUMNS", "80") testdir.makepyfile("def test_this(): assert 0, 'this_failed' * 100") + result = testdir.runpytest("-rN") + result.stdout.no_fnmatch_line("*short test summary*") result = testdir.runpytest() - assert "short test summary" not in result.stdout.str() - result = testdir.runpytest("-rf") result.stdout.fnmatch_lines( [ "*test summary*", @@ -806,13 +973,13 @@ def test_fail_extra_reporting(testdir, monkeypatch): def test_fail_reporting_on_pass(testdir): testdir.makepyfile("def test_this(): assert 1") result = testdir.runpytest("-rf") - assert "short test summary" not in result.stdout.str() + result.stdout.no_fnmatch_line("*short test summary*") def test_pass_extra_reporting(testdir): testdir.makepyfile("def test_this(): assert 1") result = testdir.runpytest() - assert "short test summary" not in result.stdout.str() + result.stdout.no_fnmatch_line("*short test summary*") result = testdir.runpytest("-rp") result.stdout.fnmatch_lines(["*test summary*", "PASS*test_pass_extra_reporting*"]) @@ -820,14 +987,21 @@ def test_pass_extra_reporting(testdir): def test_pass_reporting_on_fail(testdir): testdir.makepyfile("def test_this(): assert 0") result = testdir.runpytest("-rp") - assert "short test summary" not in result.stdout.str() + result.stdout.no_fnmatch_line("*short test summary*") def test_pass_output_reporting(testdir): testdir.makepyfile( """ + def setup_module(): + print("setup_module") + + def teardown_module(): + print("teardown_module") + def test_pass_has_output(): print("Four score and seven years ago...") + def test_pass_no_output(): pass """ @@ -842,8 +1016,12 @@ def test_pass_output_reporting(testdir): [ "*= PASSES =*", "*_ test_pass_has_output _*", + "*- Captured stdout setup -*", + "setup_module", "*- Captured stdout call -*", "Four score and seven years ago...", + "*- Captured stdout teardown -*", + "teardown_module", "*= short test summary info =*", "PASSED test_pass_output_reporting.py::test_pass_has_output", "PASSED test_pass_output_reporting.py::test_pass_no_output", @@ -852,25 +1030,76 @@ def test_pass_output_reporting(testdir): ) -def test_color_yes(testdir): - testdir.makepyfile("def test_this(): assert 1") - result = testdir.runpytest("--color=yes") - assert "test session starts" in result.stdout.str() - assert "\x1b[1m" in result.stdout.str() +def test_color_yes(testdir, color_mapping): + p1 = testdir.makepyfile( + """ + def fail(): + assert 0 + + def test_this(): + fail() + """ + ) + result = testdir.runpytest("--color=yes", str(p1)) + color_mapping.requires_ordered_markup(result) + result.stdout.fnmatch_lines( + color_mapping.format_for_fnmatch( + [ + "{bold}=*= test session starts =*={reset}", + "collected 1 item", + "", + "test_color_yes.py {red}F{reset}{red} * [100%]{reset}", + "", + "=*= FAILURES =*=", + "{red}{bold}_*_ test_this _*_{reset}", + "", + " {kw}def{hl-reset} {function}test_this{hl-reset}():", + "> fail()", + "", + "{bold}{red}test_color_yes.py{reset}:5: ", + "_ _ * _ _*", + "", + " {kw}def{hl-reset} {function}fail{hl-reset}():", + "> {kw}assert{hl-reset} {number}0{hl-reset}", + "{bold}{red}E assert 0{reset}", + "", + "{bold}{red}test_color_yes.py{reset}:2: AssertionError", + "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", + ] + ) + ) + result = testdir.runpytest("--color=yes", "--tb=short", str(p1)) + result.stdout.fnmatch_lines( + color_mapping.format_for_fnmatch( + [ + "{bold}=*= test session starts =*={reset}", + "collected 1 item", + "", + "test_color_yes.py {red}F{reset}{red} * [100%]{reset}", + "", + "=*= FAILURES =*=", + "{red}{bold}_*_ test_this _*_{reset}", + "{bold}{red}test_color_yes.py{reset}:5: in test_this", + " fail()", + "{bold}{red}test_color_yes.py{reset}:2: in fail", + " {kw}assert{hl-reset} {number}0{hl-reset}", + "{bold}{red}E assert 0{reset}", + "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", + ] + ) + ) def test_color_no(testdir): testdir.makepyfile("def test_this(): assert 1") result = testdir.runpytest("--color=no") assert "test session starts" in result.stdout.str() - assert "\x1b[1m" not in result.stdout.str() + result.stdout.no_fnmatch_line("*\x1b[1m*") @pytest.mark.parametrize("verbose", [True, False]) def test_color_yes_collection_on_non_atty(testdir, verbose): - """skip collect progress report when working on non-terminals. - #1397 - """ + """#1397: Skip collect progress report when working on non-terminals.""" testdir.makepyfile( """ import pytest @@ -885,44 +1114,69 @@ def test_color_yes_collection_on_non_atty(testdir, verbose): result = testdir.runpytest(*args) assert "test session starts" in result.stdout.str() assert "\x1b[1m" in result.stdout.str() - assert "collecting 10 items" not in result.stdout.str() + result.stdout.no_fnmatch_line("*collecting 10 items*") if verbose: assert "collecting ..." in result.stdout.str() assert "collected 10 items" in result.stdout.str() -def test_getreportopt(): - class Config(object): - class Option(object): - reportchars = "" - disable_warnings = True +def test_getreportopt() -> None: + from _pytest.terminal import _REPORTCHARS_DEFAULT + + class FakeConfig: + class Option: + reportchars = _REPORTCHARS_DEFAULT + disable_warnings = False option = Option() - config = Config() + config = cast(Config, FakeConfig()) + + assert _REPORTCHARS_DEFAULT == "fE" + + # Default. + assert getreportopt(config) == "wfE" config.option.reportchars = "sf" - assert getreportopt(config) == "sf" + assert getreportopt(config) == "wsf" config.option.reportchars = "sfxw" - assert getreportopt(config) == "sfx" + assert getreportopt(config) == "sfxw" + + config.option.reportchars = "a" + assert getreportopt(config) == "wsxXEf" + + config.option.reportchars = "N" + assert getreportopt(config) == "w" + + config.option.reportchars = "NwfE" + assert getreportopt(config) == "wfE" + + config.option.reportchars = "NfENx" + assert getreportopt(config) == "wx" # Now with --disable-warnings. - config.option.disable_warnings = False + config.option.disable_warnings = True config.option.reportchars = "a" - assert getreportopt(config) == "sxXwEf" # NOTE: "w" included! + assert getreportopt(config) == "sxXEf" config.option.reportchars = "sfx" - assert getreportopt(config) == "sfxw" + assert getreportopt(config) == "sfx" config.option.reportchars = "sfxw" - assert getreportopt(config) == "sfxw" + assert getreportopt(config) == "sfx" config.option.reportchars = "a" - assert getreportopt(config) == "sxXwEf" # NOTE: "w" included! + assert getreportopt(config) == "sxXEf" config.option.reportchars = "A" - assert getreportopt(config) == "PpsxXwEf" + assert getreportopt(config) == "PpsxXEf" + + config.option.reportchars = "AN" + assert getreportopt(config) == "" + + config.option.reportchars = "NwfE" + assert getreportopt(config) == "fE" def test_terminalreporter_reportopt_addopts(testdir): @@ -968,16 +1222,15 @@ def test_tbstyle_short(testdir): assert "assert x" in s -def test_traceconfig(testdir, monkeypatch): +def test_traceconfig(testdir): result = testdir.runpytest("--traceconfig") result.stdout.fnmatch_lines(["*active plugins*"]) - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED -class TestGenericReporting(object): - """ this test class can be subclassed with a different option - provider to run e.g. distributed tests. - """ +class TestGenericReporting: + """Test class which can be subclassed with a different option provider to + run e.g. distributed tests.""" def test_collect_fail(self, testdir, option): testdir.makepyfile("import xyz\n") @@ -999,7 +1252,31 @@ class TestGenericReporting(object): ) result = testdir.runpytest("--maxfail=2", *option.args) result.stdout.fnmatch_lines( - ["*def test_1():*", "*def test_2():*", "*2 failed*"] + [ + "*def test_1():*", + "*def test_2():*", + "*! stopping after 2 failures !*", + "*2 failed*", + ] + ) + + def test_maxfailures_with_interrupted(self, testdir): + testdir.makepyfile( + """ + def test(request): + request.session.shouldstop = "session_interrupted" + assert 0 + """ + ) + result = testdir.runpytest("--maxfail=1", "-ra") + result.stdout.fnmatch_lines( + [ + "*= short test summary info =*", + "FAILED *", + "*! stopping after 1 failures !*", + "*! session_interrupted !*", + "*= 1 failed in*", + ] ) def test_tb_option(self, testdir, option): @@ -1015,7 +1292,7 @@ class TestGenericReporting(object): ) for tbopt in ["long", "short", "no"]: print("testing --tb=%s..." % tbopt) - result = testdir.runpytest("--tb=%s" % tbopt) + result = testdir.runpytest("-rN", "--tb=%s" % tbopt) s = result.stdout.str() if tbopt == "long": assert "print(6*7)" in s @@ -1248,7 +1525,7 @@ def test_terminal_summary_warnings_are_displayed(testdir): "*== 1 failed, 2 warnings in *", ] ) - assert "None" not in result.stdout.str() + result.stdout.no_fnmatch_line("*None*") stdout = result.stdout.str() assert stdout.count("warning_from_test") == 1 assert stdout.count("=== warnings summary ") == 2 @@ -1270,15 +1547,36 @@ def test_terminal_summary_warnings_header_once(testdir): "*= warnings summary =*", "*warning_from_test*", "*= short test summary info =*", - "*== 1 failed, 1 warnings in *", + "*== 1 failed, 1 warning in *", ] ) - assert "None" not in result.stdout.str() + result.stdout.no_fnmatch_line("*None*") stdout = result.stdout.str() assert stdout.count("warning_from_test") == 1 assert stdout.count("=== warnings summary ") == 1 +@pytest.mark.filterwarnings("default") +def test_terminal_no_summary_warnings_header_once(testdir): + testdir.makepyfile( + """ + def test_failure(): + import warnings + warnings.warn("warning_from_" + "test") + assert 0 + """ + ) + result = testdir.runpytest("--no-summary") + result.stdout.no_fnmatch_line("*= warnings summary =*") + result.stdout.no_fnmatch_line("*= short test summary info =*") + + +@pytest.fixture(scope="session") +def tr() -> TerminalReporter: + config = _pytest.config._prepareconfig() + return TerminalReporter(config) + + @pytest.mark.parametrize( "exp_color, exp_line, stats_arg", [ @@ -1286,75 +1584,174 @@ def test_terminal_summary_warnings_header_once(testdir): # dict value, not the actual contents, so tuples of anything # suffice # Important statuses -- the highest priority of these always wins - ("red", "1 failed", {"failed": (1,)}), - ("red", "1 failed, 1 passed", {"failed": (1,), "passed": (1,)}), - ("red", "1 error", {"error": (1,)}), - ("red", "1 passed, 1 error", {"error": (1,), "passed": (1,)}), + ("red", [("1 failed", {"bold": True, "red": True})], {"failed": [1]}), + ( + "red", + [ + ("1 failed", {"bold": True, "red": True}), + ("1 passed", {"bold": False, "green": True}), + ], + {"failed": [1], "passed": [1]}, + ), + ("red", [("1 error", {"bold": True, "red": True})], {"error": [1]}), + ("red", [("2 errors", {"bold": True, "red": True})], {"error": [1, 2]}), + ( + "red", + [ + ("1 passed", {"bold": False, "green": True}), + ("1 error", {"bold": True, "red": True}), + ], + {"error": [1], "passed": [1]}, + ), # (a status that's not known to the code) - ("yellow", "1 weird", {"weird": (1,)}), - ("yellow", "1 passed, 1 weird", {"weird": (1,), "passed": (1,)}), - ("yellow", "1 warnings", {"warnings": (1,)}), - ("yellow", "1 passed, 1 warnings", {"warnings": (1,), "passed": (1,)}), - ("green", "5 passed", {"passed": (1, 2, 3, 4, 5)}), + ("yellow", [("1 weird", {"bold": True, "yellow": True})], {"weird": [1]}), + ( + "yellow", + [ + ("1 passed", {"bold": False, "green": True}), + ("1 weird", {"bold": True, "yellow": True}), + ], + {"weird": [1], "passed": [1]}, + ), + ("yellow", [("1 warning", {"bold": True, "yellow": True})], {"warnings": [1]}), + ( + "yellow", + [ + ("1 passed", {"bold": False, "green": True}), + ("1 warning", {"bold": True, "yellow": True}), + ], + {"warnings": [1], "passed": [1]}, + ), + ( + "green", + [("5 passed", {"bold": True, "green": True})], + {"passed": [1, 2, 3, 4, 5]}, + ), # "Boring" statuses. These have no effect on the color of the summary # line. Thus, if *every* test has a boring status, the summary line stays # at its default color, i.e. yellow, to warn the user that the test run # produced no useful information - ("yellow", "1 skipped", {"skipped": (1,)}), - ("green", "1 passed, 1 skipped", {"skipped": (1,), "passed": (1,)}), - ("yellow", "1 deselected", {"deselected": (1,)}), - ("green", "1 passed, 1 deselected", {"deselected": (1,), "passed": (1,)}), - ("yellow", "1 xfailed", {"xfailed": (1,)}), - ("green", "1 passed, 1 xfailed", {"xfailed": (1,), "passed": (1,)}), - ("yellow", "1 xpassed", {"xpassed": (1,)}), - ("green", "1 passed, 1 xpassed", {"xpassed": (1,), "passed": (1,)}), + ("yellow", [("1 skipped", {"bold": True, "yellow": True})], {"skipped": [1]}), + ( + "green", + [ + ("1 passed", {"bold": True, "green": True}), + ("1 skipped", {"bold": False, "yellow": True}), + ], + {"skipped": [1], "passed": [1]}, + ), + ( + "yellow", + [("1 deselected", {"bold": True, "yellow": True})], + {"deselected": [1]}, + ), + ( + "green", + [ + ("1 passed", {"bold": True, "green": True}), + ("1 deselected", {"bold": False, "yellow": True}), + ], + {"deselected": [1], "passed": [1]}, + ), + ("yellow", [("1 xfailed", {"bold": True, "yellow": True})], {"xfailed": [1]}), + ( + "green", + [ + ("1 passed", {"bold": True, "green": True}), + ("1 xfailed", {"bold": False, "yellow": True}), + ], + {"xfailed": [1], "passed": [1]}, + ), + ("yellow", [("1 xpassed", {"bold": True, "yellow": True})], {"xpassed": [1]}), + ( + "yellow", + [ + ("1 passed", {"bold": False, "green": True}), + ("1 xpassed", {"bold": True, "yellow": True}), + ], + {"xpassed": [1], "passed": [1]}, + ), # Likewise if no tests were found at all - ("yellow", "no tests ran", {}), + ("yellow", [("no tests ran", {"yellow": True})], {}), # Test the empty-key special case - ("yellow", "no tests ran", {"": (1,)}), - ("green", "1 passed", {"": (1,), "passed": (1,)}), + ("yellow", [("no tests ran", {"yellow": True})], {"": [1]}), + ( + "green", + [("1 passed", {"bold": True, "green": True})], + {"": [1], "passed": [1]}, + ), # A couple more complex combinations ( "red", - "1 failed, 2 passed, 3 xfailed", - {"passed": (1, 2), "failed": (1,), "xfailed": (1, 2, 3)}, + [ + ("1 failed", {"bold": True, "red": True}), + ("2 passed", {"bold": False, "green": True}), + ("3 xfailed", {"bold": False, "yellow": True}), + ], + {"passed": [1, 2], "failed": [1], "xfailed": [1, 2, 3]}, ), ( "green", - "1 passed, 2 skipped, 3 deselected, 2 xfailed", + [ + ("1 passed", {"bold": True, "green": True}), + ("2 skipped", {"bold": False, "yellow": True}), + ("3 deselected", {"bold": False, "yellow": True}), + ("2 xfailed", {"bold": False, "yellow": True}), + ], { - "passed": (1,), - "skipped": (1, 2), - "deselected": (1, 2, 3), - "xfailed": (1, 2), + "passed": [1], + "skipped": [1, 2], + "deselected": [1, 2, 3], + "xfailed": [1, 2], }, ), ], ) -def test_summary_stats(exp_line, exp_color, stats_arg): +def test_summary_stats( + tr: TerminalReporter, + exp_line: List[Tuple[str, Dict[str, bool]]], + exp_color: str, + stats_arg: Dict[str, List[object]], +) -> None: + tr.stats = stats_arg + + # Fake "_is_last_item" to be True. + class fake_session: + testscollected = 0 + + tr._session = fake_session # type: ignore[assignment] + assert tr._is_last_item + + # Reset cache. + tr._main_color = None + print("Based on stats: %s" % stats_arg) print('Expect summary: "{}"; with color "{}"'.format(exp_line, exp_color)) - (line, color) = build_summary_stats_line(stats_arg) + (line, color) = tr.build_summary_stats_line() print('Actually got: "{}"; with color "{}"'.format(line, color)) assert line == exp_line assert color == exp_color -def test_skip_counting_towards_summary(): +def test_skip_counting_towards_summary(tr): class DummyReport(BaseReport): count_towards_summary = True r1 = DummyReport() r2 = DummyReport() - res = build_summary_stats_line({"failed": (r1, r2)}) - assert res == ("2 failed", "red") + tr.stats = {"failed": (r1, r2)} + tr._main_color = None + res = tr.build_summary_stats_line() + assert res == ([("2 failed", {"bold": True, "red": True})], "red") r1.count_towards_summary = False - res = build_summary_stats_line({"failed": (r1, r2)}) - assert res == ("1 failed", "red") + tr.stats = {"failed": (r1, r2)} + tr._main_color = None + res = tr.build_summary_stats_line() + assert res == ([("1 failed", {"bold": True, "red": True})], "red") -class TestClassicOutputStyle(object): +class TestClassicOutputStyle: """Ensure classic output style works as expected (#3883)""" @pytest.fixture @@ -1400,7 +1797,7 @@ class TestClassicOutputStyle(object): result.stdout.fnmatch_lines([".F.F.", "*2 failed, 3 passed in*"]) -class TestProgressOutputStyle(object): +class TestProgressOutputStyle: @pytest.fixture def many_tests_files(self, testdir): testdir.makepyfile( @@ -1436,7 +1833,7 @@ class TestProgressOutputStyle(object): """ ) output = testdir.runpytest() - assert "ZeroDivisionError" not in output.stdout.str() + output.stdout.no_fnmatch_line("*ZeroDivisionError*") output.stdout.fnmatch_lines(["=* 2 passed in *="]) def test_normal(self, many_tests_files, testdir): @@ -1449,6 +1846,56 @@ class TestProgressOutputStyle(object): ] ) + def test_colored_progress(self, testdir, monkeypatch, color_mapping): + monkeypatch.setenv("PY_COLORS", "1") + testdir.makepyfile( + test_axfail=""" + import pytest + @pytest.mark.xfail + def test_axfail(): assert 0 + """, + test_bar=""" + import pytest + @pytest.mark.parametrize('i', range(10)) + def test_bar(i): pass + """, + test_foo=""" + import pytest + import warnings + @pytest.mark.parametrize('i', range(5)) + def test_foo(i): + warnings.warn(DeprecationWarning("collection")) + pass + """, + test_foobar=""" + import pytest + @pytest.mark.parametrize('i', range(5)) + def test_foobar(i): raise ValueError() + """, + ) + result = testdir.runpytest() + result.stdout.re_match_lines( + color_mapping.format_for_rematch( + [ + r"test_axfail.py {yellow}x{reset}{green} \s+ \[ 4%\]{reset}", + r"test_bar.py ({green}\.{reset}){{10}}{green} \s+ \[ 52%\]{reset}", + r"test_foo.py ({green}\.{reset}){{5}}{yellow} \s+ \[ 76%\]{reset}", + r"test_foobar.py ({red}F{reset}){{5}}{red} \s+ \[100%\]{reset}", + ] + ) + ) + + # Only xfail should have yellow progress indicator. + result = testdir.runpytest("test_axfail.py") + result.stdout.re_match_lines( + color_mapping.format_for_rematch( + [ + r"test_axfail.py {yellow}x{reset}{yellow} \s+ \[100%\]{reset}", + r"^{yellow}=+ ({yellow}{bold}|{bold}{yellow})1 xfailed{reset}{yellow} in ", + ] + ) + ) + def test_count(self, many_tests_files, testdir): testdir.makeini( """ @@ -1520,6 +1967,22 @@ class TestProgressOutputStyle(object): r"\[gw\d\] \[\s*\d+%\] PASSED test_foobar.py::test_foobar\[1\]", ] ) + output.stdout.fnmatch_lines_random( + [ + line.translate(TRANS_FNMATCH) + for line in [ + "test_bar.py::test_bar[0] ", + "test_foo.py::test_foo[0] ", + "test_foobar.py::test_foobar[0] ", + "[gw?] [ 5%] PASSED test_*[?] ", + "[gw?] [ 10%] PASSED test_*[?] ", + "[gw?] [ 55%] PASSED test_*[?] ", + "[gw?] [ 60%] PASSED test_*[?] ", + "[gw?] [ 95%] PASSED test_*[?] ", + "[gw?] [100%] PASSED test_*[?] ", + ] + ] + ) def test_capture_no(self, many_tests_files, testdir): output = testdir.runpytest("-s") @@ -1528,10 +1991,10 @@ class TestProgressOutputStyle(object): ) output = testdir.runpytest("--capture=no") - assert "%]" not in output.stdout.str() + output.stdout.no_fnmatch_line("*%]*") -class TestProgressWithTeardown(object): +class TestProgressWithTeardown: """Ensure we show the correct percentages for tests that fail during teardown (#3088)""" @pytest.fixture @@ -1598,15 +2061,21 @@ class TestProgressWithTeardown(object): [r"test_bar.py (\.E){5}\s+\[ 25%\]", r"test_foo.py (\.E){15}\s+\[100%\]"] ) - def test_teardown_many_verbose(self, testdir, many_files): - output = testdir.runpytest("-v") - output.stdout.re_match_lines( - [ - r"test_bar.py::test_bar\[0\] PASSED\s+\[ 5%\]", - r"test_bar.py::test_bar\[0\] ERROR\s+\[ 5%\]", - r"test_bar.py::test_bar\[4\] PASSED\s+\[ 25%\]", - r"test_bar.py::test_bar\[4\] ERROR\s+\[ 25%\]", - ] + def test_teardown_many_verbose( + self, testdir: Testdir, many_files, color_mapping + ) -> None: + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines( + color_mapping.format_for_fnmatch( + [ + "test_bar.py::test_bar[0] PASSED * [ 5%]", + "test_bar.py::test_bar[0] ERROR * [ 5%]", + "test_bar.py::test_bar[4] PASSED * [ 25%]", + "test_foo.py::test_foo[14] PASSED * [100%]", + "test_foo.py::test_foo[14] ERROR * [100%]", + "=* 20 passed, 20 errors in *", + ] + ) ) def test_xdist_normal(self, many_files, testdir, monkeypatch): @@ -1616,44 +2085,41 @@ class TestProgressWithTeardown(object): output.stdout.re_match_lines([r"[\.E]{40} \s+ \[100%\]"]) -def test_skip_reasons_folding(): +def test_skip_reasons_folding() -> None: path = "xyz" lineno = 3 message = "justso" longrepr = (path, lineno, message) - class X(object): + class X: pass - ev1 = X() + ev1 = cast(CollectReport, X()) ev1.when = "execute" ev1.skipped = True ev1.longrepr = longrepr - ev2 = X() + ev2 = cast(CollectReport, X()) ev2.when = "execute" ev2.longrepr = longrepr ev2.skipped = True # ev3 might be a collection report - ev3 = X() + ev3 = cast(CollectReport, X()) ev3.when = "collect" ev3.longrepr = longrepr ev3.skipped = True - values = _folded_skips([ev1, ev2, ev3]) + values = _folded_skips(Path.cwd(), [ev1, ev2, ev3]) assert len(values) == 1 - num, fspath, lineno, reason = values[0] + num, fspath, lineno_, reason = values[0] assert num == 3 assert fspath == path - assert lineno == lineno + assert lineno_ == lineno assert reason == message def test_line_with_reprcrash(monkeypatch): - import _pytest.terminal - from wcwidth import wcswidth - mocked_verbose_word = "FAILED" mocked_pos = "some::nodeid" @@ -1663,10 +2129,10 @@ def test_line_with_reprcrash(monkeypatch): monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos) - class config(object): + class config: pass - class rep(object): + class rep: def _get_verbose_word(self, *args): return mocked_verbose_word @@ -1677,11 +2143,11 @@ def test_line_with_reprcrash(monkeypatch): def check(msg, width, expected): __tracebackhide__ = True if msg: - rep.longrepr.reprcrash.message = msg - actual = _get_line_with_reprcrash_message(config, rep(), width) + rep.longrepr.reprcrash.message = msg # type: ignore + actual = _get_line_with_reprcrash_message(config, rep(), width) # type: ignore assert actual == expected - if actual != "%s %s" % (mocked_verbose_word, mocked_pos): + if actual != "{} {}".format(mocked_verbose_word, mocked_pos): assert len(actual) <= width assert wcswidth(actual) <= width @@ -1703,17 +2169,107 @@ def test_line_with_reprcrash(monkeypatch): check("some\nmessage", 80, "FAILED some::nodeid - some") # Test unicode safety. - check(u"😄😄😄😄😄\n2nd line", 25, u"FAILED some::nodeid - ...") - check(u"😄😄😄😄😄\n2nd line", 26, u"FAILED some::nodeid - ...") - check(u"😄😄😄😄😄\n2nd line", 27, u"FAILED some::nodeid - 😄...") - check(u"😄😄😄😄😄\n2nd line", 28, u"FAILED some::nodeid - 😄...") - check(u"😄😄😄😄😄\n2nd line", 29, u"FAILED some::nodeid - 😄😄...") + check("🉐🉐🉐🉐🉐\n2nd line", 25, "FAILED some::nodeid - ...") + check("🉐🉐🉐🉐🉐\n2nd line", 26, "FAILED some::nodeid - ...") + check("🉐🉐🉐🉐🉐\n2nd line", 27, "FAILED some::nodeid - 🉐...") + check("🉐🉐🉐🉐🉐\n2nd line", 28, "FAILED some::nodeid - 🉐...") + check("🉐🉐🉐🉐🉐\n2nd line", 29, "FAILED some::nodeid - 🉐🉐...") # NOTE: constructed, not sure if this is supported. - # It would fail if not using u"" in Python 2 for mocked_pos. - mocked_pos = u"nodeid::😄::withunicode" - check(u"😄😄😄😄😄\n2nd line", 29, u"FAILED nodeid::😄::withunicode") - check(u"😄😄😄😄😄\n2nd line", 40, u"FAILED nodeid::😄::withunicode - 😄😄...") - check(u"😄😄😄😄😄\n2nd line", 41, u"FAILED nodeid::😄::withunicode - 😄😄...") - check(u"😄😄😄😄😄\n2nd line", 42, u"FAILED nodeid::😄::withunicode - 😄😄😄...") - check(u"😄😄😄😄😄\n2nd line", 80, u"FAILED nodeid::😄::withunicode - 😄😄😄😄😄") + mocked_pos = "nodeid::🉐::withunicode" + check("🉐🉐🉐🉐🉐\n2nd line", 29, "FAILED nodeid::🉐::withunicode") + check("🉐🉐🉐🉐🉐\n2nd line", 40, "FAILED nodeid::🉐::withunicode - 🉐🉐...") + check("🉐🉐🉐🉐🉐\n2nd line", 41, "FAILED nodeid::🉐::withunicode - 🉐🉐...") + check("🉐🉐🉐🉐🉐\n2nd line", 42, "FAILED nodeid::🉐::withunicode - 🉐🉐🉐...") + check("🉐🉐🉐🉐🉐\n2nd line", 80, "FAILED nodeid::🉐::withunicode - 🉐🉐🉐🉐🉐") + + +@pytest.mark.parametrize( + "seconds, expected", + [ + (10.0, "10.00s"), + (10.34, "10.34s"), + (59.99, "59.99s"), + (60.55, "60.55s (0:01:00)"), + (123.55, "123.55s (0:02:03)"), + (60 * 60 + 0.5, "3600.50s (1:00:00)"), + ], +) +def test_format_session_duration(seconds, expected): + from _pytest.terminal import format_session_duration + + assert format_session_duration(seconds) == expected + + +def test_collecterror(testdir): + p1 = testdir.makepyfile("raise SyntaxError()") + result = testdir.runpytest("-ra", str(p1)) + result.stdout.fnmatch_lines( + [ + "collected 0 items / 1 error", + "*= ERRORS =*", + "*_ ERROR collecting test_collecterror.py _*", + "E SyntaxError: *", + "*= short test summary info =*", + "ERROR test_collecterror.py", + "*! Interrupted: 1 error during collection !*", + "*= 1 error in *", + ] + ) + + +def test_no_summary_collecterror(testdir): + p1 = testdir.makepyfile("raise SyntaxError()") + result = testdir.runpytest("-ra", "--no-summary", str(p1)) + result.stdout.no_fnmatch_line("*= ERRORS =*") + + +def test_via_exec(testdir: Testdir) -> None: + p1 = testdir.makepyfile("exec('def test_via_exec(): pass')") + result = testdir.runpytest(str(p1), "-vv") + result.stdout.fnmatch_lines( + ["test_via_exec.py::test_via_exec <- <string> PASSED*", "*= 1 passed in *"] + ) + + +class TestCodeHighlight: + def test_code_highlight_simple(self, testdir: Testdir, color_mapping) -> None: + testdir.makepyfile( + """ + def test_foo(): + assert 1 == 10 + """ + ) + result = testdir.runpytest("--color=yes") + color_mapping.requires_ordered_markup(result) + result.stdout.fnmatch_lines( + color_mapping.format_for_fnmatch( + [ + " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + "{bold}{red}E assert 1 == 10{reset}", + ] + ) + ) + + def test_code_highlight_continuation(self, testdir: Testdir, color_mapping) -> None: + testdir.makepyfile( + """ + def test_foo(): + print(''' + '''); assert 0 + """ + ) + result = testdir.runpytest("--color=yes") + color_mapping.requires_ordered_markup(result) + + result.stdout.fnmatch_lines( + color_mapping.format_for_fnmatch( + [ + " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + " {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}", + "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}", + "{bold}{red}E assert 0{reset}", + ] + ) + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py index 7622342b1d7..cc03385f3a9 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_tmpdir.py @@ -1,20 +1,26 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import errno import os import stat import sys +from typing import Callable +from typing import cast +from typing import List import attr -import six import pytest from _pytest import pathlib +from _pytest.config import Config +from _pytest.pathlib import cleanup_numbered_dir +from _pytest.pathlib import create_cleanup_lock +from _pytest.pathlib import make_numbered_dir +from _pytest.pathlib import maybe_delete_a_numbered_dir +from _pytest.pathlib import on_rm_rf_error from _pytest.pathlib import Path -from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG +from _pytest.pathlib import register_cleanup_lock_removal +from _pytest.pathlib import rm_rf +from _pytest.tmpdir import get_user +from _pytest.tmpdir import TempdirFactory +from _pytest.tmpdir import TempPathFactory def test_tmpdir_fixture(testdir): @@ -23,17 +29,9 @@ def test_tmpdir_fixture(testdir): results.stdout.fnmatch_lines(["*1 passed*"]) -def test_ensuretemp(recwarn): - d1 = pytest.ensuretemp("hello") - d2 = pytest.ensuretemp("hello") - assert d1 == d2 - assert d1.check(dir=1) - - @attr.s -class FakeConfig(object): +class FakeConfig: basetemp = attr.ib() - trace = attr.ib(default=None) @property def trace(self): @@ -47,12 +45,9 @@ class FakeConfig(object): return self -class TestTempdirHandler(object): +class TestTempdirHandler: def test_mktemp(self, tmp_path): - - from _pytest.tmpdir import TempdirFactory, TempPathFactory - - config = FakeConfig(tmp_path) + config = cast(Config, FakeConfig(tmp_path)) t = TempdirFactory(TempPathFactory.from_config(config)) tmp = t.mktemp("world") assert tmp.relto(t.getbasetemp()) == "world0" @@ -64,15 +59,13 @@ class TestTempdirHandler(object): def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): """#4425""" - from _pytest.tmpdir import TempPathFactory - monkeypatch.chdir(tmp_path) - config = FakeConfig("hello") + config = cast(Config, FakeConfig("hello")) t = TempPathFactory.from_config(config) assert t.getbasetemp().resolve() == (tmp_path / "hello").resolve() -class TestConfigTmpdir(object): +class TestConfigTmpdir: def test_getbasetemp_custom_removes_old(self, testdir): mytemp = testdir.tmpdir.join("xyz") p = testdir.makepyfile( @@ -90,18 +83,37 @@ class TestConfigTmpdir(object): assert not mytemp.join("hello").check() -def test_basetemp(testdir): +testdata = [ + ("mypath", True), + ("/mypath1", False), + ("./mypath1", True), + ("../mypath3", False), + ("../../mypath4", False), + ("mypath5/..", False), + ("mypath6/../mypath6", True), + ("mypath7/../mypath7/..", False), +] + + +@pytest.mark.parametrize("basename, is_ok", testdata) +def test_mktemp(testdir, basename, is_ok): mytemp = testdir.tmpdir.mkdir("mytemp") p = testdir.makepyfile( """ - import pytest - def test_1(): - pytest.ensuretemp("hello") - """ + def test_abs_path(tmpdir_factory): + tmpdir_factory.mktemp('{}', numbered=False) + """.format( + basename + ) ) - result = testdir.runpytest(p, "--basetemp=%s" % mytemp, SHOW_PYTEST_WARNINGS_ARG) - assert result.ret == 0 - assert mytemp.join("hello").check() + + result = testdir.runpytest(p, "--basetemp=%s" % mytemp) + if is_ok: + assert result.ret == 0 + assert mytemp.join(basename).check() + else: + assert result.ret == 1 + result.stdout.fnmatch_lines("*ValueError*") def test_tmpdir_always_is_realpath(testdir): @@ -177,7 +189,6 @@ def test_tmpdir_fallback_tox_env(testdir, monkeypatch): monkeypatch.delenv("USERNAME", raising=False) testdir.makepyfile( """ - import pytest def test_some(tmpdir): assert tmpdir.isdir() """ @@ -203,7 +214,6 @@ def test_tmpdir_fallback_uid_not_found(testdir): testdir.makepyfile( """ - import pytest def test_some(tmpdir): assert tmpdir.isdir() """ @@ -219,8 +229,6 @@ def test_get_user_uid_not_found(): user id does not correspond to a valid user (e.g. running pytest in a Docker container with 'docker run -u'. """ - from _pytest.tmpdir import get_user - assert get_user() is None @@ -230,19 +238,15 @@ def test_get_user(monkeypatch): required by getpass module are missing from the environment on Windows (#1010). """ - from _pytest.tmpdir import get_user - monkeypatch.delenv("USER", raising=False) monkeypatch.delenv("USERNAME", raising=False) assert get_user() is None -class TestNumberedDir(object): +class TestNumberedDir: PREFIX = "fun-" def test_make(self, tmp_path): - from _pytest.pathlib import make_numbered_dir - for i in range(10): d = make_numbered_dir(root=tmp_path, prefix=self.PREFIX) assert d.name.startswith(self.PREFIX) @@ -257,20 +261,16 @@ class TestNumberedDir(object): def test_cleanup_lock_create(self, tmp_path): d = tmp_path.joinpath("test") d.mkdir() - from _pytest.pathlib import create_cleanup_lock - lockfile = create_cleanup_lock(d) - with pytest.raises(EnvironmentError, match="cannot create lockfile in .*"): + with pytest.raises(OSError, match="cannot create lockfile in .*"): create_cleanup_lock(d) lockfile.unlink() - def test_lock_register_cleanup_removal(self, tmp_path): - from _pytest.pathlib import create_cleanup_lock, register_cleanup_lock_removal - + def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None: lock = create_cleanup_lock(tmp_path) - registry = [] + registry = [] # type: List[Callable[..., None]] register_cleanup_lock_removal(lock, register=registry.append) (cleanup_func,) = registry @@ -289,10 +289,8 @@ class TestNumberedDir(object): assert not lock.exists() - def _do_cleanup(self, tmp_path): + def _do_cleanup(self, tmp_path: Path) -> None: self.test_make(tmp_path) - from _pytest.pathlib import cleanup_numbered_dir - cleanup_numbered_dir( root=tmp_path, prefix=self.PREFIX, @@ -306,12 +304,9 @@ class TestNumberedDir(object): print(a, b) def test_cleanup_locked(self, tmp_path): + p = make_numbered_dir(root=tmp_path, prefix=self.PREFIX) - from _pytest import pathlib - - p = pathlib.make_numbered_dir(root=tmp_path, prefix=self.PREFIX) - - pathlib.create_cleanup_lock(p) + create_cleanup_lock(p) assert not pathlib.ensure_deletable( p, consider_lock_dead_if_created_before=p.stat().st_mtime - 1 @@ -326,16 +321,14 @@ class TestNumberedDir(object): self._do_cleanup(tmp_path) def test_removal_accepts_lock(self, tmp_path): - folder = pathlib.make_numbered_dir(root=tmp_path, prefix=self.PREFIX) - pathlib.create_cleanup_lock(folder) - pathlib.maybe_delete_a_numbered_dir(folder) + folder = make_numbered_dir(root=tmp_path, prefix=self.PREFIX) + create_cleanup_lock(folder) + maybe_delete_a_numbered_dir(folder) assert folder.is_dir() class TestRmRf: def test_rm_rf(self, tmp_path): - from _pytest.pathlib import rm_rf - adir = tmp_path / "adir" adir.mkdir() rm_rf(adir) @@ -351,8 +344,6 @@ class TestRmRf: def test_rm_rf_with_read_only_file(self, tmp_path): """Ensure rm_rf can remove directories with read-only files in them (#5524)""" - from _pytest.pathlib import rm_rf - fn = tmp_path / "dir/foo.txt" fn.parent.mkdir() @@ -370,8 +361,6 @@ class TestRmRf: def test_rm_rf_with_read_only_directory(self, tmp_path): """Ensure rm_rf can remove read-only directories (#5524)""" - from _pytest.pathlib import rm_rf - adir = tmp_path / "dir" adir.mkdir() @@ -382,9 +371,7 @@ class TestRmRf: assert not adir.is_dir() - def test_on_rm_rf_error(self, tmp_path): - from _pytest.pathlib import on_rm_rf_error - + def test_on_rm_rf_error(self, tmp_path: Path) -> None: adir = tmp_path / "dir" adir.mkdir() @@ -394,34 +381,38 @@ class TestRmRf: # unknown exception with pytest.warns(pytest.PytestWarning): - exc_info = (None, RuntimeError(), None) - on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path) + exc_info1 = (None, RuntimeError(), None) + on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path) assert fn.is_file() # we ignore FileNotFoundError - file_not_found = OSError() - file_not_found.errno = errno.ENOENT - exc_info = (None, file_not_found, None) - assert not on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path) + exc_info2 = (None, FileNotFoundError(), None) + assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path) - permission_error = OSError() - permission_error.errno = errno.EACCES # unknown function - with pytest.warns(pytest.PytestWarning): - exc_info = (None, permission_error, None) - on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path) + with pytest.warns( + pytest.PytestWarning, + match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ", + ): + exc_info3 = (None, PermissionError(), None) + on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path) + assert fn.is_file() + + # ignored function + with pytest.warns(None) as warninfo: + exc_info4 = (None, PermissionError(), None) + on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path) assert fn.is_file() + assert not [x.message for x in warninfo] - exc_info = (None, permission_error, None) - on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path) + exc_info5 = (None, PermissionError(), None) + on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path) assert not fn.is_file() def attempt_symlink_to(path, to_path): """Try to make a symlink from "path" to "to_path", skipping in case this platform does not support it or we don't have sufficient privileges (common on Windows).""" - if sys.platform.startswith("win") and six.PY2: - pytest.skip("pathlib for some reason cannot make symlinks on Python 2") try: Path(path).symlink_to(Path(to_path)) except OSError: @@ -441,7 +432,7 @@ def test_basetemp_with_read_only_files(testdir): def test(tmp_path): fn = tmp_path / 'foo.txt' - fn.write_text(u'hello') + fn.write_text('hello') mode = os.stat(str(fn)).st_mode os.chmod(str(fn), mode & ~stat.S_IREAD) """ diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py index 6b721d1c0ce..c7b6bfcec92 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_unittest.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import gc +import sys +from typing import List import pytest -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.config import ExitCode def test_simple_unittest(testdir): @@ -60,7 +57,7 @@ def test_isclasscheck_issue53(testdir): """ ) result = testdir.runpytest(testpath) - assert result.ret == EXIT_NOTESTSCOLLECTED + assert result.ret == ExitCode.NO_TESTS_COLLECTED def test_setup(testdir): @@ -238,7 +235,7 @@ def test_unittest_skip_issue148(testdir): def test_method_and_teardown_failing_reporting(testdir): testdir.makepyfile( """ - import unittest, pytest + import unittest class TC(unittest.TestCase): def tearDown(self): assert 0, "down1" @@ -275,7 +272,7 @@ def test_setup_failure_is_shown(testdir): result = testdir.runpytest("-s") assert result.ret == 1 result.stdout.fnmatch_lines(["*setUp*", "*assert 0*down1*", "*1 failed*"]) - assert "never42" not in result.stdout.str() + result.stdout.no_fnmatch_line("*never42*") def test_setup_setUpClass(testdir): @@ -347,7 +344,7 @@ def test_testcase_adderrorandfailure_defers(testdir, type): % (type, type) ) result = testdir.runpytest() - assert "should not raise" not in result.stdout.str() + result.stdout.no_fnmatch_line("*should not raise*") @pytest.mark.parametrize("type", ["Error", "Failure"]) @@ -416,7 +413,7 @@ def test_module_level_pytestmark(testdir): reprec.assertoutcome(skipped=1) -class TestTrialUnittest(object): +class TestTrialUnittest: def setup_class(cls): cls.ut = pytest.importorskip("twisted.trial.unittest") # on windows trial uses a socket for a reactor and apparently doesn't close it properly @@ -482,9 +479,6 @@ class TestTrialUnittest(object): pass """ ) - from _pytest.compat import _is_unittest_unexpected_success_a_failure - - should_fail = _is_unittest_unexpected_success_a_failure() result = testdir.runpytest("-rxs", *self.ignore_unclosed_socket_warning) result.stdout.fnmatch_lines_random( [ @@ -495,12 +489,10 @@ class TestTrialUnittest(object): "*i2wanto*", "*sys.version_info*", "*skip_in_method*", - "*1 failed*4 skipped*3 xfailed*" - if should_fail - else "*4 skipped*3 xfail*1 xpass*", + "*1 failed*4 skipped*3 xfailed*", ] ) - assert result.ret == (1 if should_fail else 0) + assert result.ret == 1 def test_trial_error(self, testdir): testdir.makepyfile( @@ -540,19 +532,31 @@ class TestTrialUnittest(object): # will crash both at test time and at teardown """ ) - result = testdir.runpytest() + # Ignore DeprecationWarning (for `cmp`) from attrs through twisted, + # for stable test results. + result = testdir.runpytest( + "-vv", "-oconsole_output_style=classic", "-W", "ignore::DeprecationWarning" + ) result.stdout.fnmatch_lines( [ + "test_trial_error.py::TC::test_four FAILED", + "test_trial_error.py::TC::test_four ERROR", + "test_trial_error.py::TC::test_one FAILED", + "test_trial_error.py::TC::test_three FAILED", + "test_trial_error.py::TC::test_two FAILED", "*ERRORS*", + "*_ ERROR at teardown of TC.test_four _*", "*DelayedCalls*", - "*test_four*", + "*= FAILURES =*", + "*_ TC.test_four _*", "*NameError*crash*", - "*test_one*", + "*_ TC.test_one _*", "*NameError*crash*", - "*test_three*", + "*_ TC.test_three _*", "*DelayedCalls*", - "*test_two*", - "*crash*", + "*_ TC.test_two _*", + "*NameError*crash*", + "*= 4 failed, 1 error in *", ] ) @@ -694,7 +698,7 @@ def test_unittest_not_shown_in_traceback(testdir): """ ) res = testdir.runpytest() - assert "failUnlessEqual" not in res.stdout.str() + res.stdout.no_fnmatch_line("*failUnlessEqual*") def test_unorderable_types(testdir): @@ -713,8 +717,8 @@ def test_unorderable_types(testdir): """ ) result = testdir.runpytest() - assert "TypeError" not in result.stdout.str() - assert result.ret == EXIT_NOTESTSCOLLECTED + result.stdout.no_fnmatch_line("*TypeError*") + assert result.ret == ExitCode.NO_TESTS_COLLECTED def test_unittest_typerror_traceback(testdir): @@ -768,22 +772,17 @@ def test_unittest_expected_failure_for_passing_test_is_fail(testdir, runner): unittest.main() """ ) - from _pytest.compat import _is_unittest_unexpected_success_a_failure - should_fail = _is_unittest_unexpected_success_a_failure() if runner == "pytest": result = testdir.runpytest("-rxX") result.stdout.fnmatch_lines( - [ - "*MyTestCase*test_passing_test_is_fail*", - "*1 failed*" if should_fail else "*1 xpassed*", - ] + ["*MyTestCase*test_passing_test_is_fail*", "*1 failed*"] ) else: result = testdir.runpython(script) result.stderr.fnmatch_lines(["*1 test in*", "*(unexpected successes=1)*"]) - assert result.ret == (1 if should_fail else 0) + assert result.ret == 1 @pytest.mark.parametrize( @@ -875,6 +874,37 @@ def test_no_teardown_if_setupclass_failed(testdir): reprec.assertoutcome(passed=1, failed=1) +def test_cleanup_functions(testdir): + """Ensure functions added with addCleanup are always called after each test ends (#6947)""" + testdir.makepyfile( + """ + import unittest + + cleanups = [] + + class Test(unittest.TestCase): + + def test_func_1(self): + self.addCleanup(cleanups.append, "test_func_1") + + def test_func_2(self): + self.addCleanup(cleanups.append, "test_func_2") + assert 0 + + def test_func_3_check_cleanups(self): + assert cleanups == ["test_func_1", "test_func_2"] + """ + ) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines( + [ + "*::test_func_1 PASSED *", + "*::test_func_2 FAILED *", + "*::test_func_3_check_cleanups PASSED *", + ] + ) + + def test_issue333_result_clearing(testdir): testdir.makeconftest( """ @@ -954,9 +984,7 @@ def test_class_method_containing_test_issue1558(testdir): reprec.assertoutcome(passed=1) -@pytest.mark.parametrize( - "base", ["six.moves.builtins.object", "unittest.TestCase", "unittest2.TestCase"] -) +@pytest.mark.parametrize("base", ["builtins.object", "unittest.TestCase"]) def test_usefixtures_marker_on_unittest(base, testdir): """#3498""" module = base.rsplit(".", 1)[0] @@ -1037,7 +1065,7 @@ def test_testcase_handles_init_exceptions(testdir): ) result = testdir.runpytest() assert "should raise this exception" in result.stdout.str() - assert "ERROR at teardown of MyTestCase.test_hello" not in result.stdout.str() + result.stdout.no_fnmatch_line("*ERROR at teardown of MyTestCase.test_hello*") def test_error_message_with_parametrized_fixtures(testdir): @@ -1065,3 +1093,175 @@ def test_setup_inheritance_skipping(testdir, test_name, expected_outcome): testdir.copy_example("unittest/{}".format(test_name)) result = testdir.runpytest() result.stdout.fnmatch_lines(["* {} in *".format(expected_outcome)]) + + +def test_BdbQuit(testdir): + testdir.makepyfile( + test_foo=""" + import unittest + + class MyTestCase(unittest.TestCase): + def test_bdbquit(self): + import bdb + raise bdb.BdbQuit() + + def test_should_not_run(self): + pass + """ + ) + reprec = testdir.inline_run() + reprec.assertoutcome(failed=1, passed=1) + + +def test_exit_outcome(testdir): + testdir.makepyfile( + test_foo=""" + import pytest + import unittest + + class MyTestCase(unittest.TestCase): + def test_exit_outcome(self): + pytest.exit("pytest_exit called") + + def test_should_not_run(self): + pass + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"]) + + +def test_trace(testdir, monkeypatch): + calls = [] + + def check_call(*args, **kwargs): + calls.append((args, kwargs)) + assert args == ("runcall",) + + class _pdb: + def runcall(*args, **kwargs): + calls.append((args, kwargs)) + + return _pdb + + monkeypatch.setattr("_pytest.debugging.pytestPDB._init_pdb", check_call) + + p1 = testdir.makepyfile( + """ + import unittest + + class MyTestCase(unittest.TestCase): + def test(self): + self.assertEqual('foo', 'foo') + """ + ) + result = testdir.runpytest("--trace", str(p1)) + assert len(calls) == 2 + assert result.ret == 0 + + +def test_pdb_teardown_called(testdir, monkeypatch) -> None: + """Ensure tearDown() is always called when --pdb is given in the command-line. + + We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling + tearDown() eventually to avoid memory leaks when using --pdb. + """ + teardowns = [] # type: List[str] + monkeypatch.setattr( + pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False + ) + + testdir.makepyfile( + """ + import unittest + import pytest + + class MyTestCase(unittest.TestCase): + + def tearDown(self): + pytest.test_pdb_teardown_called_teardowns.append(self.id()) + + def test_1(self): + pass + def test_2(self): + pass + """ + ) + result = testdir.runpytest_inprocess("--pdb") + result.stdout.fnmatch_lines("* 2 passed in *") + assert teardowns == [ + "test_pdb_teardown_called.MyTestCase.test_1", + "test_pdb_teardown_called.MyTestCase.test_2", + ] + + +@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"]) +def test_pdb_teardown_skipped(testdir, monkeypatch, mark: str) -> None: + """With --pdb, setUp and tearDown should not be called for skipped tests.""" + tracked = [] # type: List[str] + monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False) + + testdir.makepyfile( + """ + import unittest + import pytest + + class MyTestCase(unittest.TestCase): + + def setUp(self): + pytest.test_pdb_teardown_skipped.append("setUp:" + self.id()) + + def tearDown(self): + pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id()) + + {mark}("skipped for reasons") + def test_1(self): + pass + + """.format( + mark=mark + ) + ) + result = testdir.runpytest_inprocess("--pdb") + result.stdout.fnmatch_lines("* 1 skipped in *") + assert tracked == [] + + +def test_async_support(testdir): + pytest.importorskip("unittest.async_case") + + testdir.copy_example("unittest/test_unittest_asyncio.py") + reprec = testdir.inline_run() + reprec.assertoutcome(failed=1, passed=2) + + +def test_asynctest_support(testdir): + """Check asynctest support (#7110)""" + pytest.importorskip("asynctest") + + testdir.copy_example("unittest/test_unittest_asynctest.py") + reprec = testdir.inline_run() + reprec.assertoutcome(failed=1, passed=2) + + +def test_plain_unittest_does_not_support_async(testdir): + """Async functions in plain unittest.TestCase subclasses are not supported without plugins. + + This test exists here to avoid introducing this support by accident, leading users + to expect that it works, rather than doing so intentionally as a feature. + + See https://github.com/pytest-dev/pytest-asyncio/issues/180 for more context. + """ + testdir.copy_example("unittest/test_unittest_plain_async.py") + result = testdir.runpytest_subprocess() + if hasattr(sys, "pypy_version_info"): + # in PyPy we can't reliable get the warning about the coroutine not being awaited, + # because it depends on the coroutine being garbage collected; given that + # we are running in a subprocess, that's difficult to enforce + expected_lines = ["*1 passed*"] + else: + expected_lines = [ + "*RuntimeWarning: coroutine * was never awaited", + "*1 passed*", + ] + result.stdout.fnmatch_lines(expected_lines) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py new file mode 100644 index 00000000000..f16d7252a68 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_warning_types.py @@ -0,0 +1,37 @@ +import inspect + +import _pytest.warning_types +import pytest + + +@pytest.mark.parametrize( + "warning_class", + [ + w + for n, w in vars(_pytest.warning_types).items() + if inspect.isclass(w) and issubclass(w, Warning) + ], +) +def test_warning_types(warning_class): + """Make sure all warnings declared in _pytest.warning_types are displayed as coming + from 'pytest' instead of the internal module (#5452). + """ + assert warning_class.__module__ == "pytest" + + +@pytest.mark.filterwarnings("error::pytest.PytestWarning") +def test_pytest_warnings_repr_integration_test(testdir): + """Small integration test to ensure our small hack of setting the __module__ attribute + of our warnings actually works (#5452). + """ + testdir.makepyfile( + """ + import pytest + import warnings + + def test(): + warnings.warn(pytest.PytestWarning("some warning")) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["E pytest.PytestWarning: some warning"]) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py index 65f57e024f3..b7a231094fd 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/test_warnings.py @@ -1,25 +1,30 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import sys +import os import warnings - -import six +from typing import List +from typing import Optional +from typing import Tuple import pytest +from _pytest.fixtures import FixtureRequest +from _pytest.pytester import Testdir WARNINGS_SUMMARY_HEADER = "warnings summary" @pytest.fixture -def pyfile_with_warnings(testdir, request): - """ - Create a test file which calls a function in a module which generates warnings. - """ +def pyfile_with_warnings(testdir: Testdir, request: FixtureRequest) -> str: + """Create a test file which calls a function in a module which generates warnings.""" testdir.syspathinsert() test_name = request.function.__name__ module_name = test_name.lstrip("test_") + "_module" - testdir.makepyfile( + test_file = testdir.makepyfile( + """ + import {module_name} + def test_func(): + assert {module_name}.foo() == 1 + """.format( + module_name=module_name + ), **{ module_name: """ import warnings @@ -27,24 +32,16 @@ def pyfile_with_warnings(testdir, request): warnings.warn(UserWarning("user warning")) warnings.warn(RuntimeWarning("runtime warning")) return 1 - """, - test_name: """ - import {module_name} - def test_func(): - assert {module_name}.foo() == 1 - """.format( - module_name=module_name - ), - } + """, + }, ) + return str(test_file) @pytest.mark.filterwarnings("default") def test_normal_flow(testdir, pyfile_with_warnings): - """ - Check that the warnings section is displayed. - """ - result = testdir.runpytest() + """Check that the warnings section is displayed.""" + result = testdir.runpytest(pyfile_with_warnings) result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, @@ -59,7 +56,7 @@ def test_normal_flow(testdir, pyfile_with_warnings): @pytest.mark.filterwarnings("always") -def test_setup_teardown_warnings(testdir, pyfile_with_warnings): +def test_setup_teardown_warnings(testdir): testdir.makepyfile( """ import warnings @@ -100,7 +97,7 @@ def test_as_errors(testdir, pyfile_with_warnings, method): ) # Use a subprocess, since changing logging level affects other threads # (xdist). - result = testdir.runpytest_subprocess(*args) + result = testdir.runpytest_subprocess(*args, pyfile_with_warnings) result.stdout.fnmatch_lines( [ "E UserWarning: user warning", @@ -121,100 +118,34 @@ def test_ignore(testdir, pyfile_with_warnings, method): """ ) - result = testdir.runpytest(*args) + result = testdir.runpytest(*args, pyfile_with_warnings) result.stdout.fnmatch_lines(["* 1 passed in *"]) assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() -@pytest.mark.skipif( - sys.version_info < (3, 0), reason="warnings message is unicode is ok in python3" -) @pytest.mark.filterwarnings("always") -def test_unicode(testdir, pyfile_with_warnings): +def test_unicode(testdir): testdir.makepyfile( """ - # -*- coding: utf-8 -*- import warnings import pytest @pytest.fixture def fix(): - warnings.warn(u"测试") + warnings.warn("测试") yield def test_func(fix): pass - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines( - [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, - "*test_unicode.py:8: UserWarning: \u6d4b\u8bd5*", - "* 1 passed, 1 warnings*", - ] - ) - - -@pytest.mark.skipif( - sys.version_info >= (3, 0), - reason="warnings message is broken as it is not str instance", -) -def test_py2_unicode(testdir, pyfile_with_warnings): - if getattr(sys, "pypy_version_info", ())[:2] == (5, 9) and sys.platform.startswith( - "win" - ): - pytest.xfail("fails with unicode error on PyPy2 5.9 and Windows (#2905)") - testdir.makepyfile( """ - # -*- coding: utf-8 -*- - import warnings - import pytest - - - @pytest.fixture - def fix(): - warnings.warn(u"测试") - yield - - @pytest.mark.filterwarnings('always') - def test_func(fix): - pass - """ ) result = testdir.runpytest() result.stdout.fnmatch_lines( [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, - "*test_py2_unicode.py:8: UserWarning: \\u6d4b\\u8bd5", - '*warnings.warn(u"\u6d4b\u8bd5")', - "*warnings.py:*: UnicodeWarning: Warning is using unicode non*", - "* 1 passed, 2 warnings*", - ] - ) - - -def test_py2_unicode_ascii(testdir): - """Ensure that our warning about 'unicode warnings containing non-ascii messages' - does not trigger with ascii-convertible messages""" - testdir.makeini("[pytest]") - testdir.makepyfile( - """ - import pytest - import warnings - - @pytest.mark.filterwarnings('always') - def test_func(): - warnings.warn(u"hello") - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines( - [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, - '*warnings.warn(u"hello")', - "* 1 passed, 1 warnings in*", + "*test_unicode.py:7: UserWarning: \u6d4b\u8bd5*", + "* 1 passed, 1 warning*", ] ) @@ -245,9 +176,8 @@ def test_works_with_filterwarnings(testdir): @pytest.mark.parametrize("default_config", ["ini", "cmdline"]) def test_filterwarnings_mark(testdir, default_config): - """ - Test ``filterwarnings`` mark works and takes precedence over command line and ini options. - """ + """Test ``filterwarnings`` mark works and takes precedence over command + line and ini options.""" if default_config == "ini": testdir.makeini( """ @@ -273,22 +203,22 @@ def test_filterwarnings_mark(testdir, default_config): """ ) result = testdir.runpytest("-W always" if default_config == "cmdline" else "") - result.stdout.fnmatch_lines(["*= 1 failed, 2 passed, 1 warnings in *"]) + result.stdout.fnmatch_lines(["*= 1 failed, 2 passed, 1 warning in *"]) def test_non_string_warning_argument(testdir): """Non-str argument passed to warning breaks pytest (#2956)""" testdir.makepyfile( - """ + """\ import warnings import pytest def test(): - warnings.warn(UserWarning(1, u'foo')) - """ + warnings.warn(UserWarning(1, 'foo')) + """ ) result = testdir.runpytest("-W", "always") - result.stdout.fnmatch_lines(["*= 1 passed, 1 warnings in *"]) + result.stdout.fnmatch_lines(["*= 1 passed, 1 warning in *"]) def test_filterwarnings_mark_registration(testdir): @@ -310,9 +240,8 @@ def test_filterwarnings_mark_registration(testdir): def test_warning_captured_hook(testdir): testdir.makeconftest( """ - from _pytest.warnings import _issue_warning_captured def pytest_configure(config): - _issue_warning_captured(UserWarning("config warning"), config.hook, stacklevel=2) + config.issue_config_time_warning(UserWarning("config warning"), stacklevel=2) """ ) testdir.makepyfile( @@ -336,9 +265,8 @@ def test_warning_captured_hook(testdir): collected = [] class WarningCollector: - def pytest_warning_captured(self, warning_message, when, item): - imge_name = item.name if item is not None else "" - collected.append((str(warning_message.message), when, imge_name)) + def pytest_warning_recorded(self, warning_message, when, nodeid, location): + collected.append((str(warning_message.message), when, nodeid, location)) result = testdir.runpytest(plugins=[WarningCollector()]) result.stdout.fnmatch_lines(["*1 passed*"]) @@ -346,18 +274,32 @@ def test_warning_captured_hook(testdir): expected = [ ("config warning", "config", ""), ("collect warning", "collect", ""), - ("setup warning", "runtest", "test_func"), - ("call warning", "runtest", "test_func"), - ("teardown warning", "runtest", "test_func"), + ("setup warning", "runtest", "test_warning_captured_hook.py::test_func"), + ("call warning", "runtest", "test_warning_captured_hook.py::test_func"), + ("teardown warning", "runtest", "test_warning_captured_hook.py::test_func"), ] - assert collected == expected + for index in range(len(expected)): + collected_result = collected[index] + expected_result = expected[index] + + assert collected_result[0] == expected_result[0], str(collected) + assert collected_result[1] == expected_result[1], str(collected) + assert collected_result[2] == expected_result[2], str(collected) + + # NOTE: collected_result[3] is location, which differs based on the platform you are on + # thus, the best we can do here is assert the types of the paremeters match what we expect + # and not try and preload it in the expected array + if collected_result[3] is not None: + assert type(collected_result[3][0]) is str, str(collected) + assert type(collected_result[3][1]) is int, str(collected) + assert type(collected_result[3][2]) is str, str(collected) + else: + assert collected_result[3] is None, str(collected) @pytest.mark.filterwarnings("always") def test_collection_warnings(testdir): - """ - Check that we also capture warnings issued during test collection (#3251). - """ + """Check that we also capture warnings issued during test collection (#3251).""" testdir.makepyfile( """ import warnings @@ -374,7 +316,7 @@ def test_collection_warnings(testdir): "*== %s ==*" % WARNINGS_SUMMARY_HEADER, " *collection_warnings.py:3: UserWarning: collection warning", ' warnings.warn(UserWarning("collection warning"))', - "* 1 passed, 1 warnings*", + "* 1 passed, 1 warning*", ] ) @@ -430,14 +372,14 @@ def test_hide_pytest_internal_warnings(testdir, ignore_pytest_warnings): [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, "*test_hide_pytest_internal_warnings.py:4: PytestWarning: some internal warning", - "* 1 passed, 1 warnings *", + "* 1 passed, 1 warning *", ] ) @pytest.mark.parametrize("ignore_on_cmdline", [True, False]) def test_option_precedence_cmdline_over_ini(testdir, ignore_on_cmdline): - """filters defined in the command-line should take precedence over filters in ini files (#3946).""" + """Filters defined in the command-line should take precedence over filters in ini files (#3946).""" testdir.makeini( """ [pytest] @@ -548,7 +490,7 @@ class TestDeprecationWarningsByDefault: [ "*== %s ==*" % WARNINGS_SUMMARY_HEADER, "*test_hidden_by_mark.py:3: DeprecationWarning: collection", - "* 1 passed, 1 warnings*", + "* 1 passed, 1 warning*", ] ) @@ -564,45 +506,26 @@ class TestDeprecationWarningsByDefault: def test_hidden_by_system(self, testdir, monkeypatch): self.create_file(testdir) - monkeypatch.setenv(str("PYTHONWARNINGS"), str("once::UserWarning")) + monkeypatch.setenv("PYTHONWARNINGS", "once::UserWarning") result = testdir.runpytest_subprocess() assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() -@pytest.mark.skipif(not six.PY2, reason="Python 2 only issue") -def test_infinite_loop_warning_against_unicode_usage_py2(testdir): - """ - We need to be careful when raising the warning about unicode usage with "warnings.warn" - because it might be overwritten by users and this itself causes another warning (#3691). - """ - testdir.makepyfile( - """ - # -*- coding: utf-8 -*- - from __future__ import unicode_literals - import warnings - import pytest - - def _custom_showwarning(message, *a, **b): - return "WARNING: {}".format(message) - - warnings.formatwarning = _custom_showwarning +@pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) +@pytest.mark.skip( + reason="This test should be enabled again before pytest 7.0 is released" +) +def test_deprecation_warning_as_error(testdir, change_default): + """This ensures that PytestDeprecationWarnings raised by pytest are turned into errors. - @pytest.mark.filterwarnings("default") - def test_custom_warning_formatter(): - warnings.warn("¥") + This test should be enabled as part of each major release, and skipped again afterwards + to ensure our deprecations are turning into warnings as expected. """ - ) - result = testdir.runpytest_subprocess() - result.stdout.fnmatch_lines(["*1 passed, * warnings in*"]) - - -@pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) -def test_removed_in_pytest4_warning_as_error(testdir, change_default): testdir.makepyfile( """ import warnings, pytest def test(): - warnings.warn(pytest.RemovedInPytest4Warning("some warning")) + warnings.warn(pytest.PytestDeprecationWarning("some warning")) """ ) if change_default == "ini": @@ -610,12 +533,12 @@ def test_removed_in_pytest4_warning_as_error(testdir, change_default): """ [pytest] filterwarnings = - ignore::pytest.RemovedInPytest4Warning + ignore::pytest.PytestDeprecationWarning """ ) args = ( - ("-Wignore::pytest.RemovedInPytest4Warning",) + ("-Wignore::pytest.PytestDeprecationWarning",) if change_default == "cmdline" else () ) @@ -634,7 +557,7 @@ class TestAssertionWarnings: def test_tuple_warning(self, testdir): testdir.makepyfile( - """ + """\ def test_foo(): assert (1,2) """ @@ -644,48 +567,6 @@ class TestAssertionWarnings: result, "assertion is always true, perhaps remove parentheses?" ) - @staticmethod - def create_file(testdir, return_none): - testdir.makepyfile( - """ - def foo(return_none): - if return_none: - return None - else: - return False - - def test_foo(): - assert foo({return_none}) - """.format( - return_none=return_none - ) - ) - - def test_none_function_warns(self, testdir): - self.create_file(testdir, True) - result = testdir.runpytest() - self.assert_result_warns( - result, 'asserting the value None, please use "assert is None"' - ) - - def test_assert_is_none_no_warn(self, testdir): - testdir.makepyfile( - """ - def foo(): - return None - - def test_foo(): - assert foo() is None - """ - ) - result = testdir.runpytest() - result.stdout.fnmatch_lines(["*1 passed in*"]) - - def test_false_function_no_warn(self, testdir): - self.create_file(testdir, False) - result = testdir.runpytest() - result.stdout.fnmatch_lines(["*1 failed in*"]) - def test_warnings_checker_twice(): """Issue #4617""" @@ -696,20 +577,198 @@ def test_warnings_checker_twice(): warnings.warn("Message B", UserWarning) +@pytest.mark.filterwarnings("ignore::pytest.PytestExperimentalApiWarning") @pytest.mark.filterwarnings("always") def test_group_warnings_by_message(testdir): testdir.copy_example("warnings/test_group_warnings_by_message.py") result = testdir.runpytest() result.stdout.fnmatch_lines( [ - "test_group_warnings_by_message.py::test_foo[0]", - "test_group_warnings_by_message.py::test_foo[1]", - "test_group_warnings_by_message.py::test_foo[2]", - "test_group_warnings_by_message.py::test_foo[3]", - "test_group_warnings_by_message.py::test_foo[4]", - "test_group_warnings_by_message.py::test_bar", - ] + "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + "test_group_warnings_by_message.py::test_foo[[]0[]]", + "test_group_warnings_by_message.py::test_foo[[]1[]]", + "test_group_warnings_by_message.py::test_foo[[]2[]]", + "test_group_warnings_by_message.py::test_foo[[]3[]]", + "test_group_warnings_by_message.py::test_foo[[]4[]]", + "test_group_warnings_by_message.py::test_foo_1", + " */test_group_warnings_by_message.py:*: UserWarning: foo", + " warnings.warn(UserWarning(msg))", + "", + "test_group_warnings_by_message.py::test_bar[[]0[]]", + "test_group_warnings_by_message.py::test_bar[[]1[]]", + "test_group_warnings_by_message.py::test_bar[[]2[]]", + "test_group_warnings_by_message.py::test_bar[[]3[]]", + "test_group_warnings_by_message.py::test_bar[[]4[]]", + " */test_group_warnings_by_message.py:*: UserWarning: bar", + " warnings.warn(UserWarning(msg))", + "", + "-- Docs: *", + "*= 11 passed, 11 warnings *", + ], + consecutive=True, + ) + + +@pytest.mark.filterwarnings("ignore::pytest.PytestExperimentalApiWarning") +@pytest.mark.filterwarnings("always") +def test_group_warnings_by_message_summary(testdir): + testdir.copy_example("warnings/test_group_warnings_by_message_summary") + testdir.syspathinsert() + result = testdir.runpytest() + result.stdout.fnmatch_lines( + [ + "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + "test_1.py: 21 warnings", + "test_2.py: 1 warning", + " */test_1.py:7: UserWarning: foo", + " warnings.warn(UserWarning(msg))", + "", + "test_1.py: 20 warnings", + " */test_1.py:7: UserWarning: bar", + " warnings.warn(UserWarning(msg))", + "", + "-- Docs: *", + "*= 42 passed, 42 warnings *", + ], + consecutive=True, + ) + + +def test_pytest_configure_warning(testdir, recwarn): + """Issue 5115.""" + testdir.makeconftest( + """ + def pytest_configure(): + import warnings + + warnings.warn("from pytest_configure") + """ ) - warning_code = 'warnings.warn(UserWarning("foo"))' - assert warning_code in result.stdout.str() - assert result.stdout.str().count(warning_code) == 1 + + result = testdir.runpytest() + assert result.ret == 5 + assert "INTERNALERROR" not in result.stderr.str() + warning = recwarn.pop() + assert str(warning.message) == "from pytest_configure" + + +class TestStackLevel: + @pytest.fixture + def capwarn(self, testdir): + class CapturedWarnings: + captured = ( + [] + ) # type: List[Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]]] + + @classmethod + def pytest_warning_recorded(cls, warning_message, when, nodeid, location): + cls.captured.append((warning_message, location)) + + testdir.plugins = [CapturedWarnings()] + + return CapturedWarnings + + def test_issue4445_rewrite(self, testdir, capwarn): + """#4445: Make sure the warning points to a reasonable location + See origin of _issue_warning_captured at: _pytest.assertion.rewrite.py:241 + """ + testdir.makepyfile(some_mod="") + conftest = testdir.makeconftest( + """ + import some_mod + import pytest + + pytest.register_assert_rewrite("some_mod") + """ + ) + testdir.parseconfig() + + # with stacklevel=5 the warning originates from register_assert_rewrite + # function in the created conftest.py + assert len(capwarn.captured) == 1 + warning, location = capwarn.captured.pop() + file, lineno, func = location + + assert "Module already imported" in str(warning.message) + assert file == str(conftest) + assert func == "<module>" # the above conftest.py + assert lineno == 4 + + def test_issue4445_preparse(self, testdir, capwarn): + """#4445: Make sure the warning points to a reasonable location + See origin of _issue_warning_captured at: _pytest.config.__init__.py:910 + """ + testdir.makeconftest( + """ + import nothing + """ + ) + testdir.parseconfig("--help") + + # with stacklevel=2 the warning should originate from config._preparse and is + # thrown by an errorneous conftest.py + assert len(capwarn.captured) == 1 + warning, location = capwarn.captured.pop() + file, _, func = location + + assert "could not load initial conftests" in str(warning.message) + assert "config{sep}__init__.py".format(sep=os.sep) in file + assert func == "_preparse" + + @pytest.mark.filterwarnings("default") + def test_conftest_warning_captured(self, testdir: Testdir) -> None: + """Warnings raised during importing of conftest.py files is captured (#2891).""" + testdir.makeconftest( + """ + import warnings + warnings.warn(UserWarning("my custom warning")) + """ + ) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + ["conftest.py:2", "*UserWarning: my custom warning*"] + ) + + def test_issue4445_import_plugin(self, testdir, capwarn): + """#4445: Make sure the warning points to a reasonable location""" + testdir.makepyfile( + some_plugin=""" + import pytest + pytest.skip("thing", allow_module_level=True) + """ + ) + testdir.syspathinsert() + testdir.parseconfig("-p", "some_plugin") + + # with stacklevel=2 the warning should originate from + # config.PytestPluginManager.import_plugin is thrown by a skipped plugin + + assert len(capwarn.captured) == 1 + warning, location = capwarn.captured.pop() + file, _, func = location + + assert "skipped plugin 'some_plugin': thing" in str(warning.message) + assert "config{sep}__init__.py".format(sep=os.sep) in file + assert func == "_warn_about_skipped_plugins" + + def test_issue4445_issue5928_mark_generator(self, testdir): + """#4445 and #5928: Make sure the warning from an unknown mark points to + the test file where this mark is used. + """ + testfile = testdir.makepyfile( + """ + import pytest + + @pytest.mark.unknown + def test_it(): + pass + """ + ) + result = testdir.runpytest_subprocess() + # with stacklevel=2 the warning should originate from the above created test file + result.stdout.fnmatch_lines_random( + [ + "*{testfile}:3*".format(testfile=str(testfile)), + "*Unknown pytest.mark.unknown*", + ] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py new file mode 100644 index 00000000000..0a6b5ad2841 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/testing/typing_checks.py @@ -0,0 +1,24 @@ +"""File for checking typing issues. + +This file is not executed, it is only checked by mypy to ensure that +none of the code triggers any mypy errors. +""" +import pytest + + +# Issue #7488. +@pytest.mark.xfail(raises=RuntimeError) +def check_mark_xfail_raises() -> None: + pass + + +# Issue #7494. +@pytest.fixture(params=[(0, 0), (1, 1)], ids=lambda x: str(x[0])) +def check_fixture_ids_callable() -> None: + pass + + +# Issue #7494. +@pytest.mark.parametrize("func", [str, int], ids=lambda x: str(x.__name__)) +def check_parametrize_ids_callable(func) -> None: + pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/pytest/tox.ini b/tests/wpt/web-platform-tests/tools/third_party/pytest/tox.ini index f75a0610482..b42aecdf85c 100644 --- a/tests/wpt/web-platform-tests/tools/third_party/pytest/tox.ini +++ b/tests/wpt/web-platform-tests/tools/third_party/pytest/tox.ini @@ -5,101 +5,106 @@ distshare = {homedir}/.tox/distshare # make sure to update environment list in travis.yml and appveyor.yml envlist = linting - py27 - py34 py35 py36 py37 py38 py39 - pypy pypy3 - {py27,py37}-{pexpect,xdist,twisted,numpy,pluggymaster} - py27-nobyte-xdist + py37-{pexpect,xdist,unittestextras,numpy,pluggymaster} doctesting + plugins py37-freeze docs + docs-checklinks [testenv] commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:{env:_PYTEST_TOX_DEFAULT_POSARGS:}} + doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest coverage: coverage combine - coverage: coverage report -passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS + coverage: coverage report -m +passenv = USER USERNAME COVERAGE_* TRAVIS PYTEST_ADDOPTS TERM setenv = - _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_PEXPECT:} {env:_PYTEST_TOX_POSARGS_TWISTED:} {env:_PYTEST_TOX_POSARGS_XDIST:} + _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} - # Configuration to run with coverage similar to Travis/Appveyor, e.g. + # Configuration to run with coverage similar to CI, e.g. # "tox -e py37-coverage". coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess coverage: COVERAGE_FILE={toxinidir}/.coverage coverage: COVERAGE_PROCESS_START={toxinidir}/.coveragerc + doctesting: _PYTEST_TOX_POSARGS_DOCTESTING=doc/en + nobyte: PYTHONDONTWRITEBYTECODE=1 lsof: _PYTEST_TOX_POSARGS_LSOF=--lsof - pexpect: _PYTEST_TOX_PLATFORM=linux|darwin - pexpect: _PYTEST_TOX_POSARGS_PEXPECT=-m uses_pexpect - - twisted: _PYTEST_TOX_POSARGS_TWISTED=testing/test_unittest.py - xdist: _PYTEST_TOX_POSARGS_XDIST=-n auto extras = testing deps = + doctesting: PyYAML + oldattrs: attrs==17.4.0 + oldattrs: hypothesis<=4.38.1 numpy: numpy pexpect: pexpect pluggymaster: git+https://github.com/pytest-dev/pluggy.git@master - twisted: twisted - twisted: unittest2 + pygments + unittestextras: twisted + unittestextras: asynctest xdist: pytest-xdist>=1.13 {env:_PYTEST_TOX_EXTRA_DEP:} -platform = {env:_PYTEST_TOX_PLATFORM:.*} - -[testenv:py27-subprocess] -deps = - pytest-xdist>=1.13 - py27: mock - nose -commands = - pytest -n auto --runpytest=subprocess {posargs} - [testenv:linting] skip_install = True basepython = python3 deps = pre-commit>=1.11.0 -commands = pre-commit run --all-files --show-diff-on-failure +commands = pre-commit run --all-files --show-diff-on-failure {posargs:} + +[testenv:mypy] +extras = checkqa-mypy, testing +commands = mypy {posargs:src testing} + +[testenv:mypy-diff] +extras = checkqa-mypy, testing +deps = + lxml + diff-cover +commands = + -mypy --cobertura-xml-report {envtmpdir} {posargs:src testing} + diff-cover --fail-under=100 --compare-branch={env:DIFF_BRANCH:origin/{env:GITHUB_BASE_REF:master}} {envtmpdir}/cobertura.xml [testenv:docs] basepython = python3 usedevelop = True -changedir = doc/en -deps = -r{toxinidir}/doc/en/requirements.txt - +deps = + -r{toxinidir}/doc/en/requirements.txt + towncrier commands = - sphinx-build -W -b html . _build + python scripts/towncrier-draft-to-file.py + # the '-t changelog_towncrier_draft' tags makes sphinx include the draft + # changelog in the docs; this does not happen on ReadTheDocs because it uses + # the standard sphinx command so the 'changelog_towncrier_draft' is never set there + sphinx-build -W --keep-going -b html doc/en doc/en/_build/html -t changelog_towncrier_draft {posargs:} -[testenv:doctesting] +[testenv:docs-checklinks] basepython = python3 -skipsdist = True -deps = - {[testenv]deps} - PyYAML +usedevelop = True +changedir = doc/en +deps = -r{toxinidir}/doc/en/requirements.txt commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest + sphinx-build -W -q --keep-going -b linkcheck . _build [testenv:regen] changedir = doc/en -skipsdist = True basepython = python3 +passenv = SETUPTOOLS_SCM_PRETEND_VERSION deps = - sphinx + dataclasses PyYAML regendoc>=0.6.1 - dataclasses + sphinx whitelist_externals = rm make @@ -110,17 +115,47 @@ commands = rm -rf {envdir}/.pytest_cache make regen -[testenv:jython] -changedir = testing +[testenv:plugins] +# use latest versions of all plugins, including pre-releases +pip_pre=true +# use latest pip and new dependency resolver (#7783) +download=true +install_command=python -m pip --use-feature=2020-resolver install {opts} {packages} +changedir = testing/plugins_integration +deps = + anyio[curio,trio] + django + pytest-asyncio + pytest-bdd + pytest-cov + pytest-django + pytest-flakes + pytest-html + pytest-mock + pytest-sugar + pytest-trio + pytest-twisted + twisted + pytest-xvfb +setenv = + PYTHONPATH=. commands = - {envpython} {envbindir}/py.test-jython {posargs} + pip check + pytest bdd_wallet.py + pytest --cov=. simple_integration.py + pytest --ds=django_settings simple_integration.py + pytest --html=simple.html simple_integration.py + pytest pytest_anyio_integration.py + pytest pytest_asyncio_integration.py + pytest pytest_mock_integration.py + pytest pytest_trio_integration.py + pytest pytest_twisted_integration.py + pytest simple_integration.py --force-sugar --flakes [testenv:py37-freeze] changedir = testing/freeze -# Disable PEP 517 with pip, which does not work with PyInstaller currently. deps = pyinstaller - setuptools < 45.0.0 commands = {envpython} create_executable.py {envpython} tox_run.py @@ -132,68 +167,42 @@ usedevelop = True passenv = * deps = colorama - gitpython + github3.py pre-commit>=1.11.0 - towncrier wheel + towncrier commands = python scripts/release.py {posargs} -[testenv:publish_gh_release_notes] +[testenv:release-on-comment] +decription = do a release from a comment on GitHub +usedevelop = {[testenv:release]usedevelop} +passenv = {[testenv:release]passenv} +deps = {[testenv:release]deps} +commands = python scripts/release-on-comment.py {posargs} + +[testenv:publish-gh-release-notes] description = create GitHub release after deployment basepython = python3 usedevelop = True -passenv = GH_RELEASE_NOTES_TOKEN TRAVIS_TAG TRAVIS_REPO_SLUG +passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY deps = github3.py pypandoc -commands = python scripts/publish_gh_release_notes.py - - -[pytest] -minversion = 2.0 -addopts = -ra -p pytester --strict-markers -rsyncdirs = tox.ini doc src testing -python_files = test_*.py *_test.py testing/*/*.py -python_classes = Test Acceptance -python_functions = test -# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting". -testpaths = testing -norecursedirs = testing/example_scripts -xfail_strict=true -filterwarnings = - error - ignore:yield tests are deprecated, and scheduled to be removed in pytest 4.0:pytest.RemovedInPytest4Warning - ignore:Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0:pytest.RemovedInPytest4Warning - ignore::pytest.RemovedInPytest4Warning - ignore:Module already imported so cannot be rewritten:pytest.PytestWarning - # produced by path.local - ignore:bad escape.*:DeprecationWarning:re - # produced by path.readlines - ignore:.*U.*mode is deprecated:DeprecationWarning - # produced by pytest-xdist - ignore:.*type argument to addoption.*:DeprecationWarning - # produced by python >=3.5 on execnet (pytest-xdist) - ignore:.*inspect.getargspec.*deprecated, use inspect.signature.*:DeprecationWarning - # pytest's own futurewarnings - ignore::pytest.PytestExperimentalApiWarning - # Do not cause SyntaxError for invalid escape sequences in py37. - default:invalid escape sequence:DeprecationWarning - # ignore use of unregistered marks, because we use many to test the implementation - ignore::_pytest.warning_types.PytestUnknownMarkWarning -pytester_example_dir = testing/example_scripts -markers = - # dummy markers for testing - foo - bar - baz - # conftest.py reorders tests moving slow ones to the end of the list - slow - # experimental mark for all tests using pexpect - uses_pexpect +commands = python scripts/publish-gh-release-notes.py {posargs} [flake8] max-line-length = 120 -ignore = E203,W503 +extend-ignore = + ; whitespace before ':' + E203 + ; Missing Docstrings + D100,D101,D102,D103,D104,D105,D106,D107 + ; Whitespace Issues + D202,D203,D204,D205,D209,D213 + ; Quotes Issues + D302 + ; Docstring Content Issues + D400,D401,D401,D402,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D415,D416,D417 [isort] ; This config mimics what reorder-python-imports does. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/.appveyor.yml b/tests/wpt/web-platform-tests/tools/third_party/websockets/.appveyor.yml new file mode 100644 index 00000000000..7954ee4be78 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/.appveyor.yml @@ -0,0 +1,27 @@ +branches: + only: + - master + +skip_branch_with_pr: true + +environment: +# websockets only works on Python >= 3.6. + CIBW_SKIP: cp27-* cp33-* cp34-* cp35-* + CIBW_TEST_COMMAND: python -W default -m unittest + WEBSOCKETS_TESTS_TIMEOUT_FACTOR: 100 + +install: +# Ensure python is Python 3. + - set PATH=C:\Python37;%PATH% + - cmd: python -m pip install --upgrade cibuildwheel +# Create file '.cibuildwheel' so that extension build is not optional (c.f. setup.py). + - cmd: touch .cibuildwheel + +build_script: + - cmd: python -m cibuildwheel --output-dir wheelhouse +# Upload to PyPI on tags + - ps: >- + if ($env:APPVEYOR_REPO_TAG -eq "true") { + Invoke-Expression "python -m pip install twine" + Invoke-Expression "python -m twine upload --skip-existing wheelhouse/*.whl" + } diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/.circleci/config.yml b/tests/wpt/web-platform-tests/tools/third_party/websockets/.circleci/config.yml new file mode 100644 index 00000000000..0877c161ad4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/.circleci/config.yml @@ -0,0 +1,55 @@ +version: 2 + +jobs: + main: + docker: + - image: circleci/python:3.7 + steps: + # Remove IPv6 entry for localhost in Circle CI containers because it doesn't work anyway. + - run: sudo cp /etc/hosts /tmp; sudo sed -i '/::1/d' /tmp/hosts; sudo cp /tmp/hosts /etc + - checkout + - run: sudo pip install tox codecov + - run: tox -e coverage,black,flake8,isort,mypy + - run: codecov + py36: + docker: + - image: circleci/python:3.6 + steps: + # Remove IPv6 entry for localhost in Circle CI containers because it doesn't work anyway. + - run: sudo cp /etc/hosts /tmp; sudo sed -i '/::1/d' /tmp/hosts; sudo cp /tmp/hosts /etc + - checkout + - run: sudo pip install tox + - run: tox -e py36 + py37: + docker: + - image: circleci/python:3.7 + steps: + # Remove IPv6 entry for localhost in Circle CI containers because it doesn't work anyway. + - run: sudo cp /etc/hosts /tmp; sudo sed -i '/::1/d' /tmp/hosts; sudo cp /tmp/hosts /etc + - checkout + - run: sudo pip install tox + - run: tox -e py37 + py38: + docker: + - image: circleci/python:3.8.0rc1 + steps: + # Remove IPv6 entry for localhost in Circle CI containers because it doesn't work anyway. + - run: sudo cp /etc/hosts /tmp; sudo sed -i '/::1/d' /tmp/hosts; sudo cp /tmp/hosts /etc + - checkout + - run: sudo pip install tox + - run: tox -e py38 + +workflows: + version: 2 + build: + jobs: + - main + - py36: + requires: + - main + - py37: + requires: + - main + - py38: + requires: + - main diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/.github/FUNDING.yml b/tests/wpt/web-platform-tests/tools/third_party/websockets/.github/FUNDING.yml new file mode 100644 index 00000000000..7ae223b3d89 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/.github/FUNDING.yml @@ -0,0 +1 @@ +tidelift: "pypi/websockets" diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/.gitignore b/tests/wpt/web-platform-tests/tools/third_party/websockets/.gitignore new file mode 100644 index 00000000000..ef0d16520c0 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/.gitignore @@ -0,0 +1,12 @@ +*.pyc +*.so +.coverage +.mypy_cache +.tox +build/ +compliance/reports/ +dist/ +docs/_build/ +htmlcov/ +MANIFEST +websockets.egg-info/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/.readthedocs.yml b/tests/wpt/web-platform-tests/tools/third_party/websockets/.readthedocs.yml new file mode 100644 index 00000000000..109affab457 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/.readthedocs.yml @@ -0,0 +1,7 @@ +build: + image: latest + +python: + version: 3.7 + +requirements_file: docs/requirements.txt diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/.travis.yml b/tests/wpt/web-platform-tests/tools/third_party/websockets/.travis.yml new file mode 100644 index 00000000000..0306937597a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/.travis.yml @@ -0,0 +1,36 @@ +env: + global: + # websockets only works on Python >= 3.6. + - CIBW_SKIP="cp27-* cp33-* cp34-* cp35-*" + - CIBW_TEST_COMMAND="python3 -W default -m unittest" + - WEBSOCKETS_TESTS_TIMEOUT_FACTOR=100 + +matrix: + include: + - language: python + dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069) + sudo: required + python: "3.7" + services: + - docker + - os: osx + osx_image: xcode8.3 + +install: +# Python 3 is needed to run cibuildwheel for websockets. + - if [ "${TRAVIS_OS_NAME:-}" == "osx" ]; then + brew update; + brew upgrade python; + fi +# Install cibuildwheel using pip3 to make sure Python 3 is used. + - pip3 install --upgrade cibuildwheel +# Create file '.cibuildwheel' so that extension build is not optional (c.f. setup.py). + - touch .cibuildwheel + +script: + - cibuildwheel --output-dir wheelhouse +# Upload to PyPI on tags + - if [ "${TRAVIS_TAG:-}" != "" ]; then + pip3 install twine; + python3 -m twine upload --skip-existing wheelhouse/*; + fi diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/CODE_OF_CONDUCT.md b/tests/wpt/web-platform-tests/tools/third_party/websockets/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..80f80d51b11 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at aymeric DOT augustin AT fractalideas DOT com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/LICENSE b/tests/wpt/web-platform-tests/tools/third_party/websockets/LICENSE new file mode 100644 index 00000000000..b2962adba2b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2013-2019 Aymeric Augustin and contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of websockets nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/MANIFEST.in b/tests/wpt/web-platform-tests/tools/third_party/websockets/MANIFEST.in new file mode 100644 index 00000000000..1c660b95b14 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/MANIFEST.in @@ -0,0 +1,2 @@ +include LICENSE +include src/websockets/py.typed diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/Makefile b/tests/wpt/web-platform-tests/tools/third_party/websockets/Makefile new file mode 100644 index 00000000000..d9e16fefe37 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/Makefile @@ -0,0 +1,29 @@ +.PHONY: default style test coverage build clean + +export PYTHONASYNCIODEBUG=1 +export PYTHONPATH=src + +default: coverage style + +style: + isort --recursive src tests + black src tests + flake8 src tests + mypy --strict src + +test: + python -W default -m unittest + +coverage: + python -m coverage erase + python -W default -m coverage run -m unittest + python -m coverage html + python -m coverage report --show-missing --fail-under=100 + +build: + python setup.py build_ext --inplace + +clean: + find . -name '*.pyc' -o -name '*.so' -delete + find . -name __pycache__ -delete + rm -rf .coverage build compliance/reports dist docs/_build htmlcov MANIFEST src/websockets.egg-info diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/README.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/README.rst new file mode 100644 index 00000000000..1e15ba1981f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/README.rst @@ -0,0 +1,154 @@ +.. image:: logo/horizontal.svg + :width: 480px + :alt: websockets + +|rtd| |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |circleci| |codecov| + +.. |rtd| image:: https://readthedocs.org/projects/websockets/badge/?version=latest + :target: https://websockets.readthedocs.io/ + +.. |pypi-v| image:: https://img.shields.io/pypi/v/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pypi-l| image:: https://img.shields.io/pypi/l/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |circleci| image:: https://img.shields.io/circleci/project/github/aaugustin/websockets.svg + :target: https://circleci.com/gh/aaugustin/websockets + +.. |codecov| image:: https://codecov.io/gh/aaugustin/websockets/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aaugustin/websockets + +What is ``websockets``? +----------------------- + +``websockets`` is a library for building WebSocket servers_ and clients_ in +Python with a focus on correctness and simplicity. + +.. _servers: https://github.com/aaugustin/websockets/blob/master/example/server.py +.. _clients: https://github.com/aaugustin/websockets/blob/master/example/client.py + +Built on top of ``asyncio``, Python's standard asynchronous I/O framework, it +provides an elegant coroutine-based API. + +`Documentation is available on Read the Docs. <https://websockets.readthedocs.io/>`_ + +Here's how a client sends and receives messages: + +.. copy-pasted because GitHub doesn't support the include directive + +.. code:: python + + #!/usr/bin/env python + + import asyncio + import websockets + + async def hello(uri): + async with websockets.connect(uri) as websocket: + await websocket.send("Hello world!") + await websocket.recv() + + asyncio.get_event_loop().run_until_complete( + hello('ws://localhost:8765')) + +And here's an echo server: + +.. code:: python + + #!/usr/bin/env python + + import asyncio + import websockets + + async def echo(websocket, path): + async for message in websocket: + await websocket.send(message) + + asyncio.get_event_loop().run_until_complete( + websockets.serve(echo, 'localhost', 8765)) + asyncio.get_event_loop().run_forever() + +Does that look good? + +`Get started with the tutorial! <https://websockets.readthedocs.io/en/stable/intro.html>`_ + +.. raw:: html + + <hr> + <img align="left" height="150" width="150" src="https://raw.githubusercontent.com/aaugustin/websockets/master/logo/tidelift.png"> + <h3 align="center"><i>websockets for enterprise</i></h3> + <p align="center"><i>Available as part of the Tidelift Subscription</i></p> + <p align="center"><i>The maintainers of websockets and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. <a href="https://tidelift.com/subscription/pkg/pypi-websockets?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=readme">Learn more.</a></i></p> + <hr> + <p>(If you contribute to <code>websockets</code> and would like to become an official support provider, <a href="https://fractalideas.com/">let me know</a>.)</p> + +Why should I use ``websockets``? +-------------------------------- + +The development of ``websockets`` is shaped by four principles: + +1. **Simplicity**: all you need to understand is ``msg = await ws.recv()`` and + ``await ws.send(msg)``; ``websockets`` takes care of managing connections + so you can focus on your application. + +2. **Robustness**: ``websockets`` is built for production; for example it was + the only library to `handle backpressure correctly`_ before the issue + became widely known in the Python community. + +3. **Quality**: ``websockets`` is heavily tested. Continuous integration fails + under 100% branch coverage. Also it passes the industry-standard `Autobahn + Testsuite`_. + +4. **Performance**: memory use is configurable. An extension written in C + accelerates expensive operations. It's pre-compiled for Linux, macOS and + Windows and packaged in the wheel format for each system and Python version. + +Documentation is a first class concern in the project. Head over to `Read the +Docs`_ and see for yourself. + +.. _Read the Docs: https://websockets.readthedocs.io/ +.. _handle backpressure correctly: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#websocket-servers +.. _Autobahn Testsuite: https://github.com/aaugustin/websockets/blob/master/compliance/README.rst + +Why shouldn't I use ``websockets``? +----------------------------------- + +* If you prefer callbacks over coroutines: ``websockets`` was created to + provide the best coroutine-based API to manage WebSocket connections in + Python. Pick another library for a callback-based API. +* If you're looking for a mixed HTTP / WebSocket library: ``websockets`` aims + at being an excellent implementation of :rfc:`6455`: The WebSocket Protocol + and :rfc:`7692`: Compression Extensions for WebSocket. Its support for HTTP + is minimal — just enough for a HTTP health check. +* If you want to use Python 2: ``websockets`` builds upon ``asyncio`` which + only works on Python 3. ``websockets`` requires Python ≥ 3.6.1. + +What else? +---------- + +Bug reports, patches and suggestions are welcome! + +To report a security vulnerability, please use the `Tidelift security +contact`_. Tidelift will coordinate the fix and disclosure. + +.. _Tidelift security contact: https://tidelift.com/security + +For anything else, please open an issue_ or send a `pull request`_. + +.. _issue: https://github.com/aaugustin/websockets/issues/new +.. _pull request: https://github.com/aaugustin/websockets/compare/ + +Participants must uphold the `Contributor Covenant code of conduct`_. + +.. _Contributor Covenant code of conduct: https://github.com/aaugustin/websockets/blob/master/CODE_OF_CONDUCT.md + +``websockets`` is released under the `BSD license`_. + +.. _BSD license: https://github.com/aaugustin/websockets/blob/master/LICENSE diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/README.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/README.rst new file mode 100644 index 00000000000..8570f9176d5 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/README.rst @@ -0,0 +1,50 @@ +Autobahn Testsuite +================== + +General information and installation instructions are available at +https://github.com/crossbario/autobahn-testsuite. + +To improve performance, you should compile the C extension first:: + + $ python setup.py build_ext --inplace + +Running the test suite +---------------------- + +All commands below must be run from the directory containing this file. + +To test the server:: + + $ PYTHONPATH=.. python test_server.py + $ wstest -m fuzzingclient + +To test the client:: + + $ wstest -m fuzzingserver + $ PYTHONPATH=.. python test_client.py + +Run the first command in a shell. Run the second command in another shell. +It should take about ten minutes to complete — wstest is the bottleneck. +Then kill the first one with Ctrl-C. + +The test client or server shouldn't display any exceptions. The results are +stored in reports/clients/index.html. + +Note that the Autobahn software only supports Python 2, while ``websockets`` +only supports Python 3; you need two different environments. + +Conformance notes +----------------- + +Some test cases are more strict than the RFC. Given the implementation of the +library and the test echo client or server, ``websockets`` gets a "Non-Strict" +in these cases. + +In 3.2, 3.3, 4.1.3, 4.1.4, 4.2.3, 4.2.4, and 5.15 ``websockets`` notices the +protocol error and closes the connection before it has had a chance to echo +the previous frame. + +In 6.4.3 and 6.4.4, even though it uses an incremental decoder, ``websockets`` +doesn't notice the invalid utf-8 fast enough to get a "Strict" pass. These +tests are more strict than the RFC. + diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/fuzzingclient.json b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/fuzzingclient.json new file mode 100644 index 00000000000..202ff49a03a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/fuzzingclient.json @@ -0,0 +1,11 @@ + +{ + "options": {"failByDrop": false}, + "outdir": "./reports/servers", + + "servers": [{"agent": "websockets", "url": "ws://localhost:8642", "options": {"version": 18}}], + + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/fuzzingserver.json b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/fuzzingserver.json new file mode 100644 index 00000000000..1bdb42723ef --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/fuzzingserver.json @@ -0,0 +1,12 @@ + +{ + "url": "ws://localhost:8642", + + "options": {"failByDrop": false}, + "outdir": "./reports/clients", + "webport": 8080, + + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/test_client.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/test_client.py new file mode 100644 index 00000000000..5fd0f4b4fb3 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/test_client.py @@ -0,0 +1,49 @@ +import json +import logging +import urllib.parse + +import asyncio +import websockets + + +logging.basicConfig(level=logging.WARNING) + +# Uncomment this line to make only websockets more verbose. +# logging.getLogger('websockets').setLevel(logging.DEBUG) + + +SERVER = "ws://127.0.0.1:8642" +AGENT = "websockets" + + +async def get_case_count(server): + uri = f"{server}/getCaseCount" + async with websockets.connect(uri) as ws: + msg = ws.recv() + return json.loads(msg) + + +async def run_case(server, case, agent): + uri = f"{server}/runCase?case={case}&agent={agent}" + async with websockets.connect(uri, max_size=2 ** 25, max_queue=1) as ws: + async for msg in ws: + await ws.send(msg) + + +async def update_reports(server, agent): + uri = f"{server}/updateReports?agent={agent}" + async with websockets.connect(uri): + pass + + +async def run_tests(server, agent): + cases = await get_case_count(server) + for case in range(1, cases + 1): + print(f"Running test case {case} out of {cases}", end="\r") + await run_case(server, case, agent) + print(f"Ran {cases} test cases ") + await update_reports(server, agent) + + +main = run_tests(SERVER, urllib.parse.quote(AGENT)) +asyncio.get_event_loop().run_until_complete(main) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/test_server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/test_server.py new file mode 100644 index 00000000000..8020f68d35f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/compliance/test_server.py @@ -0,0 +1,27 @@ +import logging + +import asyncio +import websockets + + +logging.basicConfig(level=logging.WARNING) + +# Uncomment this line to make only websockets more verbose. +# logging.getLogger('websockets').setLevel(logging.DEBUG) + + +HOST, PORT = "127.0.0.1", 8642 + + +async def echo(ws, path): + async for msg in ws: + await ws.send(msg) + + +start_server = websockets.serve(echo, HOST, PORT, max_size=2 ** 25, max_queue=1) + +try: + asyncio.get_event_loop().run_until_complete(start_server) + asyncio.get_event_loop().run_forever() +except KeyboardInterrupt: + pass diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/Makefile b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/Makefile new file mode 100644 index 00000000000..bb25aa49d08 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/Makefile @@ -0,0 +1,160 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# 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 " 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 " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " spelling to check for typos in documentation" + +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/websockets.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/websockets.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/websockets" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/websockets" + @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." + +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." + +spelling: + $(SPHINXBUILD) -b spelling $(ALLSPHINXOPTS) $(BUILDDIR)/spelling + @echo + @echo "Check finished. Wrong words can be found in " \ + "$(BUILDDIR)/spelling/output.txt." diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/_static/tidelift.png b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/_static/tidelift.png Binary files differnew file mode 100644 index 00000000000..317dc4d9852 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/_static/tidelift.png diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/_static/websockets.svg b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/_static/websockets.svg new file mode 100644 index 00000000000..b07fb223873 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/_static/websockets.svg @@ -0,0 +1,31 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="480" height="320" viewBox="0 0 480 320"> + <linearGradient id="w" x1="0.2333" y1="0" x2="0.5889" y2="0.5333"> + <stop offset="0%" stop-color="#ffe873" /> + <stop offset="100%" stop-color="#ffd43b" /> + </linearGradient> + <linearGradient id="s" x1="0.2333" y1="0" x2="0.5889" y2="0.5333"> + <stop offset="0%" stop-color="#5a9fd4" /> + <stop offset="100%" stop-color="#306998" /> + </linearGradient> + <g> + <path fill="url(#w)" d="m 263.40708,146.81618 c -0.43704,0.0747 -0.88656,0.12978 -1.35572,0.14933 -2.45813,0.0764 -4.25357,-0.58665 -5.82335,-2.15107 l -8.89246,-8.85942 -11.23464,-11.19805 -36.04076,-35.919454 c -3.43568,-3.42217 -7.33248,-5.347474 -11.58962,-5.723468 -2.22981,-0.198219 -4.47388,0.03111 -6.64036,0.675545 -3.24213,0.944875 -6.13552,2.664848 -8.59366,5.116366 -3.83437,3.819499 -5.86349,8.414979 -5.87598,13.287801 -0.0607,4.95281 1.95153,9.60074 5.8082,13.44424 l 55.62289,55.43648 c 1.82219,1.84175 2.65971,3.79549 2.63384,6.14568 l 0.004,0.208 c 0.0527,2.43196 -0.75991,4.34571 -2.6267,6.20612 -1.78028,1.77598 -3.8094,2.65241 -6.30945,2.75552 -2.45814,0.0764 -4.25446,-0.58844 -5.82514,-2.15286 L 160.50255,128.2618 c -5.21417,-5.19459 -11.7029,-6.98745 -18.22998,-5.04881 -3.2457,0.9431 -6.13553,2.66307 -8.59545,5.11459 -3.83437,3.82127 -5.86527,8.41676 -5.87597,13.28957 -0.0562,4.95281 1.95152,9.60252 5.80641,13.4478 l 58.10689,57.90577 c 8.31984,8.29143 19.34042,11.9376 32.74331,10.83806 12.57967,-1.02043 23.02317,-5.5848 31.03441,-13.57313 7.51265,-7.4861 11.96423,-16.35175 13.28695,-26.42537 10.47206,-1.68264 19.29494,-6.04524 26.27512,-13.00158 4.01364,-3.99994 7.14963,-8.3972 9.40531,-13.16157 -14.15569,-0.39911 -28.23645,-4.00972 -41.05247,-10.83095 z" /> + <path fill="url(#s)" d="m 308.76038,138.11854 c 0.10259,-12.84514 -4.43017,-23.98541 -13.50635,-33.1346 L 259.37292,69.225372 c -0.24349,-0.240885 -0.46469,-0.487992 -0.68678,-0.744877 -1.48416,-1.739529 -2.18788,-3.583056 -2.21018,-5.807022 -0.0259,-2.470184 0.84911,-4.508375 2.7605,-6.407902 1.91406,-1.909304 3.8531,-2.737735 6.36564,-2.684403 2.53662,0.024 4.62728,0.943097 6.57257,2.881734 l 60.59178,60.384848 12.11408,-12.06914 c 1.12203,-0.90755 1.95777,-1.76887 2.87823,-2.93418 5.91879,-7.515442 5.26947,-18.272611 -1.51003,-25.028952 L 299.00456,29.727312 c -9.19393,-9.157192 -20.36703,-13.776677 -33.16789,-13.7269 -12.94266,-0.05067 -24.14163,4.548375 -33.28739,13.662901 -9.02892,8.996307 -13.64015,19.93925 -13.7008,32.487501 l -0.004,0.14222 c -0.002,0.167998 -0.005,0.336884 -0.005,0.506659 -0.091,12.232701 4.10729,22.95787 12.48154,31.881285 0.40226,0.43022 0.80274,0.85777 1.22283,1.27821 l 35.75088,35.626122 c 1.88909,1.88174 2.71769,3.79638 2.69361,6.20968 l 0.003,0.20977 c 0.0527,2.43197 -0.76081,4.34571 -2.6276,6.20791 -1.44759,1.43909 -3.06286,2.27818 -4.9564,2.60262 12.81601,6.82123 26.89677,10.43184 41.05246,10.83362 2.80598,-5.92525 4.2509,-12.41848 4.29906,-19.43526 z" /> + <path fill="#ffffff" d="m 327.48093,85.181572 c 2.84701,-2.838179 7.46359,-2.836401 10.30883,0 2.84433,2.834623 2.84612,7.435446 -0.002,10.270956 -2.84345,2.83818 -7.46271,2.83818 -10.30704,0 -2.84524,-2.83551 -2.84791,-7.435444 0,-10.270956 z" /> + </g> + <g> + <g fill="#ffd43b"> + <path d="m 25.719398,284.91839 c 0,2.59075 0.912299,4.79875 2.736898,6.62269 1.824599,1.82657 4.033255,2.73821 6.625313,2.73821 2.591402,0 4.800058,-0.91164 6.624002,-2.73821 1.825254,-1.82394 2.738209,-4.03194 2.738209,-6.62269 v -21.77984 c 0,-1.32126 0.475811,-2.45901 1.42809,-3.40998 0.952278,-0.95359 2.089375,-1.43006 3.411947,-1.43006 h 0.0793 c 1.348132,0 2.471467,0.47647 3.371969,1.43006 0.952278,0.95097 1.428745,2.08938 1.428745,3.40998 v 21.77984 c 0,2.59075 0.912299,4.79875 2.738209,6.62269 1.823944,1.82657 4.031289,2.73821 6.624002,2.73821 2.618274,0 4.839382,-0.91164 6.663981,-2.73821 1.825254,-1.82394 2.738209,-4.03194 2.738209,-6.62269 v -21.77984 c 0,-1.32126 0.475156,-2.45901 1.42809,-3.40998 0.897881,-0.95359 2.022526,-1.43006 3.371969,-1.43006 h 0.07865 c 1.323228,0 2.460325,0.47647 3.411948,1.43006 0.926062,0.95097 1.388766,2.08938 1.388766,3.40998 v 21.77984 c 0,5.26211 -1.865233,9.75807 -5.593077,13.48657 -3.729156,3.7285 -8.22577,5.59373 -13.487876,5.59373 -6.294998,0 -11.028207,-2.08807 -14.202904,-6.26747 -3.199602,4.1794 -7.94723,6.26747 -14.240916,6.26747 -5.262763,0 -9.759377,-1.86523 -13.487876,-5.59373 C 17.866544,294.67646 16,290.18115 16,284.91839 v -21.77984 c 0,-1.32126 0.476467,-2.45901 1.428745,-3.40998 0.951623,-0.95359 2.075612,-1.43006 3.371969,-1.43006 h 0.11928 c 1.295702,0 2.419036,0.47647 3.372625,1.43006 0.950967,0.95097 1.427434,2.08938 1.427434,3.40998 v 21.77984 z" /> + <path d="m 132.94801,271.6291 c 0.31786,0.66063 0.47712,1.33371 0.47712,2.02252 0,0.55577 -0.10551,1.11089 -0.3172,1.66665 -0.45026,1.24262 -1.29636,2.14181 -2.53898,2.69692 -3.70293,1.66665 -8.56853,3.8622 -14.59875,6.58599 -7.48453,3.38442 -11.87497,5.38139 -13.17067,5.9909 2.00942,2.53832 5.14414,3.80715 9.40219,3.80715 2.82931,0 5.39515,-0.83234 7.69556,-2.499 2.24798,-1.63977 3.82222,-3.75537 4.72141,-6.34808 0.76746,-2.16868 2.30107,-3.25269 4.60148,-3.25269 1.63912,0 2.94859,0.68881 3.92708,2.06185 0.6082,0.84742 0.9123,1.7335 0.9123,2.65891 0,0.55577 -0.10552,1.12399 -0.31655,1.70532 -1.56048,4.52348 -4.29869,8.17334 -8.21135,10.95087 -3.96706,2.88108 -8.41059,4.32293 -13.32993,4.32293 -6.29434,0 -11.67639,-2.23356 -16.145474,-6.70395 -4.469743,-4.46975 -6.704615,-9.85114 -6.704615,-16.14679 0,-6.29434 2.234872,-11.67507 6.704615,-16.14678 4.468434,-4.46843 9.851134,-6.70396 16.145474,-6.70396 4.54773,0 8.70027,1.24392 12.45629,3.7285 3.72785,2.43607 6.49162,5.63437 8.29,9.60274 z m -20.74695,-3.5332 c -3.64985,0 -6.7577,1.28391 -9.32289,3.84909 -2.53897,2.5665 -3.808452,5.67435 -3.808452,9.32289 v 0.27789 l 22.175692,-9.95731 c -1.95633,-2.32597 -4.97177,-3.49256 -9.04435,-3.49256 z" /> + <path d="m 146.11999,242.03442 c 1.2957,0 2.41904,0.46336 3.37197,1.38876 0.95228,0.95097 1.42874,2.08938 1.42874,3.4113 v 15.4311 c 2.98792,-2.64318 7.36525,-3.96707 13.13004,-3.96707 6.29434,0 11.67638,2.23488 16.14613,6.70396 4.46908,4.47106 6.70461,9.85245 6.70461,16.14679 0,6.29499 -2.23553,11.67638 -6.70461,16.14678 -4.46909,4.4704 -9.85113,6.70396 -16.14613,6.70396 -6.295,0 -11.66262,-2.22111 -16.10549,-6.66529 -4.4704,-4.41469 -6.71838,-9.77052 -6.7446,-16.06617 v -34.43341 c 0,-1.32257 0.47647,-2.46032 1.42875,-3.41129 0.95162,-0.92541 2.07561,-1.38877 3.37197,-1.38877 h 0.11862 z m 17.93009,26.06148 c -3.64919,0 -6.75704,1.28391 -9.32288,3.84909 -2.53767,2.5665 -3.80781,5.67435 -3.80781,9.32289 0,3.62364 1.27014,6.71772 3.80781,9.28291 2.56584,2.56519 5.67303,3.84778 9.32288,3.84778 3.62364,0 6.71773,-1.28259 9.28357,-3.84778 2.56387,-2.56519 3.84712,-5.65927 3.84712,-9.28291 0,-3.64788 -1.28325,-6.75639 -3.84712,-9.32289 -2.56584,-2.56518 -5.65927,-3.84909 -9.28357,-3.84909 z" /> + </g> + <g fill="#306998"> + <path d="m 205.94246,268.01922 c -1.16397,0 -2.14247,0.39586 -2.93548,1.18888 -0.79368,0.82054 -1.19019,1.79838 -1.19019,2.93548 0,1.58735 0.76681,2.77753 2.30172,3.56989 0.52825,0.29165 2.7369,0.95228 6.62466,1.98386 3.14717,0.89985 5.48691,2.07627 7.02051,3.53057 2.19621,2.09003 3.29267,5.06549 3.29267,8.92704 0,3.80714 -1.34879,7.0736 -4.04571,9.79739 -2.72444,2.69823 -5.9909,4.04636 -9.7987,4.04636 h -10.35381 c -1.29701,0 -2.41969,-0.47516 -3.37262,-1.42875 -0.95228,-0.89853 -1.42875,-2.02252 -1.42875,-3.37065 v -0.0806 c 0,-1.32126 0.47647,-2.45901 1.42875,-3.41129 0.95227,-0.95228 2.07561,-1.42874 3.37262,-1.42874 h 10.75032 c 1.16331,0 2.14246,-0.39586 2.93548,-1.18888 0.79368,-0.79367 1.19019,-1.77151 1.19019,-2.93548 0,-1.45561 -0.7537,-2.55339 -2.26044,-3.29201 -0.3965,-0.18678 -2.61892,-0.84742 -6.66529,-1.98386 -3.14782,-0.9254 -5.48887,-2.14377 -7.02247,-3.65051 -2.19555,-2.1418 -3.29202,-5.17035 -3.29202,-9.08432 0,-3.80846 1.34945,-7.06049 4.04702,-9.75807 2.72314,-2.72379 5.99024,-4.087 9.79805,-4.087 h 7.2997 c 1.32192,0 2.45967,0.47647 3.41195,1.43006 0.95162,0.95097 1.42809,2.08938 1.42809,3.40998 v 0.0793 c 0,1.34945 -0.47647,2.47409 -1.42809,3.37263 -0.95228,0.95097 -2.09003,1.42874 -3.41195,1.42874 z" /> + <path d="m 249.06434,258.29851 c 6.29434,0 11.67573,2.23488 16.14612,6.70396 4.46909,4.47106 6.70396,9.85245 6.70396,16.14679 0,6.29499 -2.23487,11.67638 -6.70396,16.14678 -4.46974,4.46974 -9.85178,6.70396 -16.14612,6.70396 -6.29435,0 -11.67639,-2.23356 -16.14548,-6.70396 -4.46974,-4.46974 -6.70461,-9.85113 -6.70461,-16.14678 0,-6.29434 2.23487,-11.67508 6.70461,-16.14679 4.46909,-4.46908 9.85113,-6.70396 16.14548,-6.70396 z m 0,9.79739 c -3.64986,0 -6.7577,1.28391 -9.32289,3.84909 -2.53963,2.5665 -3.80911,5.67435 -3.80911,9.32289 0,3.62364 1.26948,6.71772 3.80911,9.28291 2.56519,2.56519 5.67238,3.84778 9.32289,3.84778 3.62298,0 6.71706,-1.28259 9.28291,-3.84778 2.56518,-2.56519 3.84778,-5.65927 3.84778,-9.28291 0,-3.64788 -1.2826,-6.75639 -3.84778,-9.32289 -2.56585,-2.56518 -5.65928,-3.84909 -9.28291,-3.84909 z" /> + <path d="m 307.22146,259.37007 c 2.24864,0.71438 3.37263,2.24798 3.37263,4.60148 v 0.19989 c 0,1.6116 -0.64884,2.89419 -1.94454,3.84778 -0.89919,0.63376 -1.82525,0.95097 -2.77622,0.95097 -0.50334,0 -1.01913,-0.0793 -1.54737,-0.23791 -1.29636,-0.42272 -2.63204,-0.63638 -4.00638,-0.63638 -3.64986,0 -6.75836,1.28391 -9.32289,3.84909 -2.53963,2.5665 -3.80846,5.67435 -3.80846,9.32289 0,3.62364 1.26883,6.71772 3.80846,9.28291 2.56453,2.56519 5.67238,3.84778 9.32289,3.84778 1.375,0 2.71068,-0.21103 4.00638,-0.63507 0.50203,-0.1586 1.00471,-0.2379 1.50739,-0.2379 0.97718,0 1.91767,0.31851 2.81686,0.95358 1.2957,0.95097 1.94453,2.24798 1.94453,3.88776 0,2.32728 -1.12464,3.86089 -3.37262,4.60148 -2.22111,0.6875 -4.52152,1.03027 -6.90189,1.03027 -6.29434,0 -11.67638,-2.23356 -16.14678,-6.70396 -4.46843,-4.46974 -6.70396,-9.85113 -6.70396,-16.14678 0,-6.29435 2.23487,-11.67508 6.70396,-16.14679 4.46974,-4.46843 9.85178,-6.70396 16.14678,-6.70396 2.37906,0.001 4.68012,0.35981 6.90123,1.07287 z" /> + <path d="m 322.25671,242.03442 c 1.29504,0 2.41903,0.46336 3.37262,1.38876 0.95163,0.95097 1.42809,2.08938 1.42809,3.4113 v 27.49154 h 1.50739 c 3.38508,0 6.33301,-1.12399 8.84708,-3.37263 2.45901,-2.24798 3.86023,-5.0242 4.20431,-8.33063 0.15861,-1.24261 0.68816,-2.26174 1.58735,-3.0541 0.89854,-0.84611 1.96944,-1.27015 3.21271,-1.27015 h 0.11863 c 1.40252,0 2.5796,0.53021 3.53122,1.58735 0.84676,0.92541 1.26949,1.99697 1.26949,3.21271 0,0.15861 -0.0138,0.33163 -0.0393,0.51579 -0.63507,6.63842 -3.17405,11.61019 -7.61692,14.91531 2.32663,1.43006 4.46909,3.84909 6.42739,7.26039 2.03563,3.51746 3.05476,7.31412 3.05476,11.38473 v 2.02515 c 0,1.34813 -0.47712,2.47147 -1.42809,3.37066 -0.95359,0.95359 -2.07692,1.42874 -3.37263,1.42874 h -0.11928 c -1.29635,0 -2.41969,-0.47515 -3.37196,-1.42874 -0.95228,-0.89854 -1.42809,-2.02253 -1.42809,-3.37066 v -2.02515 c -0.0275,-3.59414 -1.31012,-6.67708 -3.84844,-9.24358 -2.56584,-2.53832 -5.66058,-3.80715 -9.28291,-3.80715 h -3.25269 v 15.07523 c 0,1.34813 -0.47646,2.47146 -1.42809,3.37065 -0.95293,0.95359 -2.07758,1.42875 -3.37262,1.42875 h -0.12059 c -1.2957,0 -2.41838,-0.47516 -3.37132,-1.42875 -0.95162,-0.89853 -1.42809,-2.02252 -1.42809,-3.37065 v -52.36547 c 0,-1.32257 0.47647,-2.46032 1.42809,-3.41129 0.95228,-0.92541 2.07562,-1.38877 3.37132,-1.38877 h 0.12059 z" /> + <path d="m 402.31164,271.6291 c 0.31721,0.66063 0.47581,1.33371 0.47581,2.02252 0,0.55577 -0.10617,1.11089 -0.31655,1.66665 -0.45025,1.24262 -1.29635,2.14181 -2.53897,2.69692 -3.70294,1.66665 -8.56919,3.8622 -14.59876,6.58599 -7.48452,3.38442 -11.87496,5.38139 -13.17067,5.9909 2.00877,2.53832 5.14349,3.80715 9.40219,3.80715 2.82866,0 5.3945,-0.83234 7.69622,-2.499 2.24732,-1.63977 3.82091,-3.75537 4.7201,-6.34808 0.76681,-2.16868 2.30172,-3.25269 4.60148,-3.25269 1.63978,0 2.94924,0.68881 3.92839,2.06185 0.60689,0.84742 0.91165,1.7335 0.91165,2.65891 0,0.55577 -0.10552,1.12399 -0.31721,1.70532 -1.56048,4.52348 -4.29738,8.17334 -8.21135,10.95087 -3.96706,2.88108 -8.40994,4.32293 -13.32928,4.32293 -6.29434,0 -11.67638,-2.23356 -16.14547,-6.70395 -4.46974,-4.46975 -6.70461,-9.85114 -6.70461,-16.14679 0,-6.29434 2.23487,-11.67507 6.70461,-16.14678 4.46843,-4.46843 9.85113,-6.70396 16.14547,-6.70396 4.54774,0 8.70093,1.24392 12.4563,3.7285 3.7285,2.43607 6.49161,5.63437 8.29065,9.60274 z m -20.7476,-3.5332 c -3.6492,0 -6.7577,1.28391 -9.32289,3.84909 -2.53897,2.5665 -3.80846,5.67435 -3.80846,9.32289 v 0.27789 l 22.1757,-9.95731 c -1.95699,-2.32597 -4.97177,-3.49256 -9.04435,-3.49256 z" /> + <path d="m 415.48166,242.03442 c 1.2957,0 2.41969,0.46336 3.37262,1.38876 0.95162,0.95097 1.42809,2.08938 1.42809,3.4113 v 11.46403 h 5.95092 c 1.2957,0 2.41903,0.47647 3.37262,1.43006 0.95163,0.95097 1.42678,2.08938 1.42678,3.40998 v 0.0793 c 0,1.34945 -0.47515,2.47409 -1.42678,3.37263 -0.95293,0.95097 -2.07692,1.42874 -3.37262,1.42874 h -5.95092 v 23.52252 c 0,0.76811 0.26347,1.41695 0.79367,1.94453 0.5289,0.53021 1.19019,0.79368 1.98321,0.79368 h 3.17404 c 1.2957,0 2.41903,0.47646 3.37262,1.42874 0.95163,0.95228 1.42678,2.09003 1.42678,3.41129 v 0.0806 c 0,1.34813 -0.47515,2.47146 -1.42678,3.37065 C 428.65298,303.52484 427.52899,304 426.23329,304 h -3.17404 c -3.43817,0 -6.38675,-1.21574 -8.84642,-3.6492 -2.43411,-2.45901 -3.6492,-5.39515 -3.6492,-8.80775 v -44.70726 c 0,-1.32258 0.47581,-2.46033 1.42809,-3.4113 0.95228,-0.9254 2.07627,-1.38876 3.37197,-1.38876 h 0.11797 z" /> + <path d="m 448.88545,268.01922 c -1.16397,0 -2.14246,0.39586 -2.93548,1.18888 -0.79368,0.82054 -1.19019,1.79838 -1.19019,2.93548 0,1.58735 0.76681,2.77753 2.30042,3.56989 0.5302,0.29165 2.7382,0.95228 6.62596,1.98386 3.14652,0.89985 5.48691,2.07627 7.02117,3.53057 2.19489,2.09003 3.29267,5.06549 3.29267,8.92704 0,3.80714 -1.34945,7.0736 -4.04637,9.79739 -2.72379,2.69823 -5.99089,4.04636 -9.79869,4.04636 h -10.35382 c -1.29635,0 -2.41969,-0.47516 -3.37262,-1.42875 -0.95228,-0.89853 -1.42744,-2.02252 -1.42744,-3.37065 v -0.0806 c 0,-1.32126 0.47516,-2.45901 1.42744,-3.41129 0.95228,-0.95228 2.07627,-1.42874 3.37262,-1.42874 h 10.75032 c 1.16332,0 2.14312,-0.39586 2.93549,-1.18888 0.79367,-0.79367 1.19018,-1.77151 1.19018,-2.93548 0,-1.45561 -0.7537,-2.55339 -2.26043,-3.29201 -0.39782,-0.18678 -2.61893,-0.84742 -6.66529,-1.98386 -3.14783,-0.9254 -5.48887,-2.14377 -7.02248,-3.65051 -2.19555,-2.1418 -3.29201,-5.17035 -3.29201,-9.08432 0,-3.80846 1.34944,-7.06049 4.04701,-9.75807 2.72314,-2.72379 5.99025,-4.087 9.7987,-4.087 h 7.29906 c 1.32322,0 2.45967,0.47647 3.41129,1.43006 0.95228,0.95097 1.42809,2.08938 1.42809,3.40998 v 0.0793 c 0,1.34945 -0.47581,2.47409 -1.42809,3.37263 -0.95162,0.95097 -2.08872,1.42874 -3.41129,1.42874 z" /> + </g> + </g> +</svg> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/api.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/api.rst new file mode 100644 index 00000000000..d265a91c2cd --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/api.rst @@ -0,0 +1,152 @@ +API +=== + +Design +------ + +``websockets`` provides complete client and server implementations, as shown +in the :doc:`getting started guide <intro>`. These functions are built on top +of low-level APIs reflecting the two phases of the WebSocket protocol: + +1. An opening handshake, in the form of an HTTP Upgrade request; + +2. Data transfer, as framed messages, ending with a closing handshake. + +The first phase is designed to integrate with existing HTTP software. +``websockets`` provides a minimal implementation to build, parse and validate +HTTP requests and responses. + +The second phase is the core of the WebSocket protocol. ``websockets`` +provides a complete implementation on top of ``asyncio`` with a simple API. + +For convenience, public APIs can be imported directly from the +:mod:`websockets` package, unless noted otherwise. Anything that isn't listed +in this document is a private API. + +High-level +---------- + +Server +...... + +.. automodule:: websockets.server + + .. autofunction:: serve(ws_handler, host=None, port=None, *, create_protocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, compression='deflate', origins=None, extensions=None, subprotocols=None, extra_headers=None, process_request=None, select_subprotocol=None, **kwds) + :async: + + .. autofunction:: unix_serve(ws_handler, path, *, create_protocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, compression='deflate', origins=None, extensions=None, subprotocols=None, extra_headers=None, process_request=None, select_subprotocol=None, **kwds) + :async: + + + .. autoclass:: WebSocketServer + + .. automethod:: close + .. automethod:: wait_closed + .. autoattribute:: sockets + + .. autoclass:: WebSocketServerProtocol(ws_handler, ws_server, *, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, origins=None, extensions=None, subprotocols=None, extra_headers=None, process_request=None, select_subprotocol=None) + + .. automethod:: handshake + .. automethod:: process_request + .. automethod:: select_subprotocol + +Client +...... + +.. automodule:: websockets.client + + .. autofunction:: connect(uri, *, create_protocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, compression='deflate', origin=None, extensions=None, subprotocols=None, extra_headers=None, **kwds) + :async: + + .. autofunction:: unix_connect(path, uri="ws://localhost/", *, create_protocol=None, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, compression='deflate', origin=None, extensions=None, subprotocols=None, extra_headers=None, **kwds) + :async: + + .. autoclass:: WebSocketClientProtocol(*, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None, origin=None, extensions=None, subprotocols=None, extra_headers=None) + + .. automethod:: handshake + +Shared +...... + +.. automodule:: websockets.protocol + + .. autoclass:: WebSocketCommonProtocol(*, ping_interval=20, ping_timeout=20, close_timeout=10, max_size=2 ** 20, max_queue=2 ** 5, read_limit=2 ** 16, write_limit=2 ** 16, loop=None) + + .. automethod:: close + .. automethod:: wait_closed + + .. automethod:: recv + .. automethod:: send + + .. automethod:: ping + .. automethod:: pong + + .. autoattribute:: local_address + .. autoattribute:: remote_address + + .. autoattribute:: open + .. autoattribute:: closed + +Types +..... + +.. automodule:: websockets.typing + + .. autodata:: Data + + +Per-Message Deflate Extension +............................. + +.. automodule:: websockets.extensions.permessage_deflate + + .. autoclass:: ServerPerMessageDeflateFactory + + .. autoclass:: ClientPerMessageDeflateFactory + +HTTP Basic Auth +............... + +.. automodule:: websockets.auth + + .. autofunction:: basic_auth_protocol_factory + + .. autoclass:: BasicAuthWebSocketServerProtocol + + .. automethod:: process_request + +Exceptions +.......... + +.. automodule:: websockets.exceptions + :members: + +Low-level +--------- + +Opening handshake +................. + +.. automodule:: websockets.handshake + :members: + +Data transfer +............. + +.. automodule:: websockets.framing + :members: + +URI parser +.......... + +.. automodule:: websockets.uri + :members: + +Utilities +......... + +.. automodule:: websockets.headers + :members: + +.. automodule:: websockets.http + :members: diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/changelog.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/changelog.rst new file mode 100644 index 00000000000..04f18a7657d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/changelog.rst @@ -0,0 +1,563 @@ +Changelog +--------- + +.. currentmodule:: websockets + +8.2 +... + +*In development* + +8.1 +... + +* Added compatibility with Python 3.8. + +8.0.2 +..... + +* Restored the ability to pass a socket with the ``sock`` parameter of + :func:`~server.serve`. + +* Removed an incorrect assertion when a connection drops. + +8.0.1 +..... + +* Restored the ability to import ``WebSocketProtocolError`` from + ``websockets``. + +8.0 +... + +.. warning:: + + **Version 8.0 drops compatibility with Python 3.4 and 3.5.** + +.. note:: + + **Version 8.0 expects** ``process_request`` **to be a coroutine.** + + Previously, it could be a function or a coroutine. + + If you're passing a ``process_request`` argument to :func:`~server.serve` + or :class:`~server.WebSocketServerProtocol`, or if you're overriding + :meth:`~protocol.WebSocketServerProtocol.process_request` in a subclass, + define it with ``async def`` instead of ``def``. + + For backwards compatibility, functions are still mostly supported, but + mixing functions and coroutines won't work in some inheritance scenarios. + +.. note:: + + **Version 8.0 changes the behavior of the** ``max_queue`` **parameter.** + + If you were setting ``max_queue=0`` to make the queue of incoming messages + unbounded, change it to ``max_queue=None``. + +.. note:: + + **Version 8.0 deprecates the** ``host`` **,** ``port`` **, and** ``secure`` + **attributes of** :class:`~protocol.WebSocketCommonProtocol`. + + Use :attr:`~protocol.WebSocketCommonProtocol.local_address` in servers and + :attr:`~protocol.WebSocketCommonProtocol.remote_address` in clients + instead of ``host`` and ``port``. + +.. note:: + + **Version 8.0 renames the** ``WebSocketProtocolError`` **exception** + to :exc:`ProtocolError` **.** + + A ``WebSocketProtocolError`` alias provides backwards compatibility. + +.. note:: + + **Version 8.0 adds the reason phrase to the return type of the low-level + API** :func:`~http.read_response` **.** + +Also: + +* :meth:`~protocol.WebSocketCommonProtocol.send`, + :meth:`~protocol.WebSocketCommonProtocol.ping`, and + :meth:`~protocol.WebSocketCommonProtocol.pong` support bytes-like types + :class:`bytearray` and :class:`memoryview` in addition to :class:`bytes`. + +* Added :exc:`~exceptions.ConnectionClosedOK` and + :exc:`~exceptions.ConnectionClosedError` subclasses of + :exc:`~exceptions.ConnectionClosed` to tell apart normal connection + termination from errors. + +* Added :func:`~auth.basic_auth_protocol_factory` to enforce HTTP Basic Auth + on the server side. + +* :func:`~client.connect` handles redirects from the server during the + handshake. + +* :func:`~client.connect` supports overriding ``host`` and ``port``. + +* Added :func:`~client.unix_connect` for connecting to Unix sockets. + +* Improved support for sending fragmented messages by accepting asynchronous + iterators in :meth:`~protocol.WebSocketCommonProtocol.send`. + +* Prevented spurious log messages about :exc:`~exceptions.ConnectionClosed` + exceptions in keepalive ping task. If you were using ``ping_timeout=None`` + as a workaround, you can remove it. + +* Changed :meth:`WebSocketServer.close() <server.WebSocketServer.close>` to + perform a proper closing handshake instead of failing the connection. + +* Avoided a crash when a ``extra_headers`` callable returns ``None``. + +* Improved error messages when HTTP parsing fails. + +* Enabled readline in the interactive client. + +* Added type hints (:pep:`484`). + +* Added a FAQ to the documentation. + +* Added documentation for extensions. + +* Documented how to optimize memory usage. + +* Improved API documentation. + +7.0 +... + +.. warning:: + + **Version 7.0 renames the** ``timeout`` **argument of** + :func:`~server.serve()` **and** :func:`~client.connect` **to** + ``close_timeout`` **.** + + This prevents confusion with ``ping_timeout``. + + For backwards compatibility, ``timeout`` is still supported. + +.. warning:: + + **Version 7.0 changes how a server terminates connections when it's + closed with** :meth:`~server.WebSocketServer.close` **.** + + Previously, connections handlers were canceled. Now, connections are + closed with close code 1001 (going away). From the perspective of the + connection handler, this is the same as if the remote endpoint was + disconnecting. This removes the need to prepare for + :exc:`~asyncio.CancelledError` in connection handlers. + + You can restore the previous behavior by adding the following line at the + beginning of connection handlers:: + + def handler(websocket, path): + closed = asyncio.ensure_future(websocket.wait_closed()) + closed.add_done_callback(lambda task: task.cancel()) + +.. note:: + + **Version 7.0 changes how a** :meth:`~protocol.WebSocketCommonProtocol.ping` + **that hasn't received a pong yet behaves when the connection is closed.** + + The ping — as in ``ping = await websocket.ping()`` — used to be canceled + when the connection is closed, so that ``await ping`` raised + :exc:`~asyncio.CancelledError`. Now ``await ping`` raises + :exc:`~exceptions.ConnectionClosed` like other public APIs. + +.. note:: + + **Version 7.0 raises a** :exc:`RuntimeError` **exception if two coroutines + call** :meth:`~protocol.WebSocketCommonProtocol.recv` **concurrently.** + + Concurrent calls lead to non-deterministic behavior because there are no + guarantees about which coroutine will receive which message. + +Also: + +* ``websockets`` sends Ping frames at regular intervals and closes the + connection if it doesn't receive a matching Pong frame. See + :class:`~protocol.WebSocketCommonProtocol` for details. + +* Added ``process_request`` and ``select_subprotocol`` arguments to + :func:`~server.serve` and :class:`~server.WebSocketServerProtocol` to + customize :meth:`~server.WebSocketServerProtocol.process_request` and + :meth:`~server.WebSocketServerProtocol.select_subprotocol` without + subclassing :class:`~server.WebSocketServerProtocol`. + +* Added support for sending fragmented messages. + +* Added the :meth:`~protocol.WebSocketCommonProtocol.wait_closed` method to + protocols. + +* Added an interactive client: ``python -m websockets <uri>``. + +* Changed the ``origins`` argument to represent the lack of an origin with + ``None`` rather than ``''``. + +* Fixed a data loss bug in :meth:`~protocol.WebSocketCommonProtocol.recv`: + canceling it at the wrong time could result in messages being dropped. + +* Improved handling of multiple HTTP headers with the same name. + +* Improved error messages when a required HTTP header is missing. + +6.0 +... + +.. warning:: + + **Version 6.0 introduces the** :class:`~http.Headers` **class for managing + HTTP headers and changes several public APIs:** + + * :meth:`~server.WebSocketServerProtocol.process_request` now receives a + :class:`~http.Headers` instead of a :class:`~http.client.HTTPMessage` in + the ``request_headers`` argument. + + * The :attr:`~protocol.WebSocketCommonProtocol.request_headers` and + :attr:`~protocol.WebSocketCommonProtocol.response_headers` attributes of + :class:`~protocol.WebSocketCommonProtocol` are :class:`~http.Headers` + instead of :class:`~http.client.HTTPMessage`. + + * The :attr:`~protocol.WebSocketCommonProtocol.raw_request_headers` and + :attr:`~protocol.WebSocketCommonProtocol.raw_response_headers` + attributes of :class:`~protocol.WebSocketCommonProtocol` are removed. + Use :meth:`~http.Headers.raw_items` instead. + + * Functions defined in the :mod:`~handshake` module now receive + :class:`~http.Headers` in argument instead of ``get_header`` or + ``set_header`` functions. This affects libraries that rely on + low-level APIs. + + * Functions defined in the :mod:`~http` module now return HTTP headers as + :class:`~http.Headers` instead of lists of ``(name, value)`` pairs. + + Since :class:`~http.Headers` and :class:`~http.client.HTTPMessage` provide + similar APIs, this change won't affect most of the code dealing with HTTP + headers. + + +Also: + +* Added compatibility with Python 3.7. + +5.0.1 +..... + +* Fixed a regression in the 5.0 release that broke some invocations of + :func:`~server.serve()` and :func:`~client.connect`. + +5.0 +... + +.. note:: + + **Version 5.0 fixes a security issue introduced in version 4.0.** + + Version 4.0 was vulnerable to denial of service by memory exhaustion + because it didn't enforce ``max_size`` when decompressing compressed + messages (`CVE-2018-1000518`_). + + .. _CVE-2018-1000518: https://nvd.nist.gov/vuln/detail/CVE-2018-1000518 + +.. note:: + + **Version 5.0 adds a** ``user_info`` **field to the return value of** + :func:`~uri.parse_uri` **and** :class:`~uri.WebSocketURI` **.** + + If you're unpacking :class:`~exceptions.WebSocketURI` into four variables, + adjust your code to account for that fifth field. + +Also: + +* :func:`~client.connect` performs HTTP Basic Auth when the URI contains + credentials. + +* Iterating on incoming messages no longer raises an exception when the + connection terminates with close code 1001 (going away). + +* A plain HTTP request now receives a 426 Upgrade Required response and + doesn't log a stack trace. + +* :func:`~server.unix_serve` can be used as an asynchronous context manager on + Python ≥ 3.5.1. + +* Added the :attr:`~protocol.WebSocketCommonProtocol.closed` property to + protocols. + +* If a :meth:`~protocol.WebSocketCommonProtocol.ping` doesn't receive a pong, + it's canceled when the connection is closed. + +* Reported the cause of :exc:`~exceptions.ConnectionClosed` exceptions. + +* Added new examples in the documentation. + +* Updated documentation with new features from Python 3.6. + +* Improved several other sections of the documentation. + +* Fixed missing close code, which caused :exc:`TypeError` on connection close. + +* Fixed a race condition in the closing handshake that raised + :exc:`~exceptions.InvalidState`. + +* Stopped logging stack traces when the TCP connection dies prematurely. + +* Prevented writing to a closing TCP connection during unclean shutdowns. + +* Made connection termination more robust to network congestion. + +* Prevented processing of incoming frames after failing the connection. + +4.0.1 +..... + +* Fixed issues with the packaging of the 4.0 release. + +4.0 +... + +.. warning:: + + **Version 4.0 enables compression with the permessage-deflate extension.** + + In August 2017, Firefox and Chrome support it, but not Safari and IE. + + Compression should improve performance but it increases RAM and CPU use. + + If you want to disable compression, add ``compression=None`` when calling + :func:`~server.serve()` or :func:`~client.connect`. + +.. warning:: + + **Version 4.0 drops compatibility with Python 3.3.** + +.. note:: + + **Version 4.0 removes the** ``state_name`` **attribute of protocols.** + + Use ``protocol.state.name`` instead of ``protocol.state_name``. + +Also: + +* :class:`~protocol.WebSocketCommonProtocol` instances can be used as + asynchronous iterators on Python ≥ 3.6. They yield incoming messages. + +* Added :func:`~server.unix_serve` for listening on Unix sockets. + +* Added the :attr:`~server.WebSocketServer.sockets` attribute to the return + value of :func:`~server.serve`. + +* Reorganized and extended documentation. + +* Aborted connections if they don't close within the configured ``timeout``. + +* Rewrote connection termination to increase robustness in edge cases. + +* Stopped leaking pending tasks when :meth:`~asyncio.Task.cancel` is called on + a connection while it's being closed. + +* Reduced verbosity of "Failing the WebSocket connection" logs. + +* Allowed ``extra_headers`` to override ``Server`` and ``User-Agent`` headers. + +3.4 +... + +* Renamed :func:`~server.serve()` and :func:`~client.connect`'s ``klass`` + argument to ``create_protocol`` to reflect that it can also be a callable. + For backwards compatibility, ``klass`` is still supported. + +* :func:`~server.serve` can be used as an asynchronous context manager on + Python ≥ 3.5.1. + +* Added support for customizing handling of incoming connections with + :meth:`~server.WebSocketServerProtocol.process_request`. + +* Made read and write buffer sizes configurable. + +* Rewrote HTTP handling for simplicity and performance. + +* Added an optional C extension to speed up low-level operations. + +* An invalid response status code during :func:`~client.connect` now raises + :class:`~exceptions.InvalidStatusCode` with a ``code`` attribute. + +* Providing a ``sock`` argument to :func:`~client.connect` no longer + crashes. + +3.3 +... + +* Ensured compatibility with Python 3.6. + +* Reduced noise in logs caused by connection resets. + +* Avoided crashing on concurrent writes on slow connections. + +3.2 +... + +* Added ``timeout``, ``max_size``, and ``max_queue`` arguments to + :func:`~client.connect()` and :func:`~server.serve`. + +* Made server shutdown more robust. + +3.1 +... + +* Avoided a warning when closing a connection before the opening handshake. + +* Added flow control for incoming data. + +3.0 +... + +.. warning:: + + **Version 3.0 introduces a backwards-incompatible change in the** + :meth:`~protocol.WebSocketCommonProtocol.recv` **API.** + + **If you're upgrading from 2.x or earlier, please read this carefully.** + + :meth:`~protocol.WebSocketCommonProtocol.recv` used to return ``None`` + when the connection was closed. This required checking the return value of + every call:: + + message = await websocket.recv() + if message is None: + return + + Now it raises a :exc:`~exceptions.ConnectionClosed` exception instead. + This is more Pythonic. The previous code can be simplified to:: + + message = await websocket.recv() + + When implementing a server, which is the more popular use case, there's no + strong reason to handle such exceptions. Let them bubble up, terminate the + handler coroutine, and the server will simply ignore them. + + In order to avoid stranding projects built upon an earlier version, the + previous behavior can be restored by passing ``legacy_recv=True`` to + :func:`~server.serve`, :func:`~client.connect`, + :class:`~server.WebSocketServerProtocol`, or + :class:`~client.WebSocketClientProtocol`. ``legacy_recv`` isn't documented + in their signatures but isn't scheduled for deprecation either. + +Also: + +* :func:`~client.connect` can be used as an asynchronous context manager on + Python ≥ 3.5.1. + +* Updated documentation with ``await`` and ``async`` syntax from Python 3.5. + +* :meth:`~protocol.WebSocketCommonProtocol.ping` and + :meth:`~protocol.WebSocketCommonProtocol.pong` support data passed as + :class:`str` in addition to :class:`bytes`. + +* Worked around an asyncio bug affecting connection termination under load. + +* Made ``state_name`` attribute on protocols a public API. + +* Improved documentation. + +2.7 +... + +* Added compatibility with Python 3.5. + +* Refreshed documentation. + +2.6 +... + +* Added ``local_address`` and ``remote_address`` attributes on protocols. + +* Closed open connections with code 1001 when a server shuts down. + +* Avoided TCP fragmentation of small frames. + +2.5 +... + +* Improved documentation. + +* Provided access to handshake request and response HTTP headers. + +* Allowed customizing handshake request and response HTTP headers. + +* Supported running on a non-default event loop. + +* Returned a 403 status code instead of 400 when the request Origin isn't + allowed. + +* Canceling :meth:`~protocol.WebSocketCommonProtocol.recv` no longer drops + the next message. + +* Clarified that the closing handshake can be initiated by the client. + +* Set the close code and reason more consistently. + +* Strengthened connection termination by simplifying the implementation. + +* Improved tests, added tox configuration, and enforced 100% branch coverage. + +2.4 +... + +* Added support for subprotocols. + +* Supported non-default event loop. + +* Added ``loop`` argument to :func:`~client.connect` and + :func:`~server.serve`. + +2.3 +... + +* Improved compliance of close codes. + +2.2 +... + +* Added support for limiting message size. + +2.1 +... + +* Added ``host``, ``port`` and ``secure`` attributes on protocols. + +* Added support for providing and checking Origin_. + +.. _Origin: https://tools.ietf.org/html/rfc6455#section-10.2 + +2.0 +... + +.. warning:: + + **Version 2.0 introduces a backwards-incompatible change in the** + :meth:`~protocol.WebSocketCommonProtocol.send`, + :meth:`~protocol.WebSocketCommonProtocol.ping`, and + :meth:`~protocol.WebSocketCommonProtocol.pong` **APIs.** + + **If you're upgrading from 1.x or earlier, please read this carefully.** + + These APIs used to be functions. Now they're coroutines. + + Instead of:: + + websocket.send(message) + + you must now write:: + + await websocket.send(message) + +Also: + +* Added flow control for outgoing data. + +1.0 +... + +* Initial public release. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/cheatsheet.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/cheatsheet.rst new file mode 100644 index 00000000000..f897326a6ba --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/cheatsheet.rst @@ -0,0 +1,109 @@ +Cheat sheet +=========== + +.. currentmodule:: websockets + +Server +------ + +* Write a coroutine that handles a single connection. It receives a WebSocket + protocol instance and the URI path in argument. + + * Call :meth:`~protocol.WebSocketCommonProtocol.recv` and + :meth:`~protocol.WebSocketCommonProtocol.send` to receive and send + messages at any time. + + * When :meth:`~protocol.WebSocketCommonProtocol.recv` or + :meth:`~protocol.WebSocketCommonProtocol.send` raises + :exc:`~exceptions.ConnectionClosed`, clean up and exit. If you started + other :class:`asyncio.Task`, terminate them before exiting. + + * If you aren't awaiting :meth:`~protocol.WebSocketCommonProtocol.recv`, + consider awaiting :meth:`~protocol.WebSocketCommonProtocol.wait_closed` + to detect quickly when the connection is closed. + + * You may :meth:`~protocol.WebSocketCommonProtocol.ping` or + :meth:`~protocol.WebSocketCommonProtocol.pong` if you wish but it isn't + needed in general. + +* Create a server with :func:`~server.serve` which is similar to asyncio's + :meth:`~asyncio.AbstractEventLoop.create_server`. You can also use it as an + asynchronous context manager. + + * The server takes care of establishing connections, then lets the handler + execute the application logic, and finally closes the connection after the + handler exits normally or with an exception. + + * For advanced customization, you may subclass + :class:`~server.WebSocketServerProtocol` and pass either this subclass or + a factory function as the ``create_protocol`` argument. + +Client +------ + +* Create a client with :func:`~client.connect` which is similar to asyncio's + :meth:`~asyncio.BaseEventLoop.create_connection`. You can also use it as an + asynchronous context manager. + + * For advanced customization, you may subclass + :class:`~server.WebSocketClientProtocol` and pass either this subclass or + a factory function as the ``create_protocol`` argument. + +* Call :meth:`~protocol.WebSocketCommonProtocol.recv` and + :meth:`~protocol.WebSocketCommonProtocol.send` to receive and send messages + at any time. + +* You may :meth:`~protocol.WebSocketCommonProtocol.ping` or + :meth:`~protocol.WebSocketCommonProtocol.pong` if you wish but it isn't + needed in general. + +* If you aren't using :func:`~client.connect` as a context manager, call + :meth:`~protocol.WebSocketCommonProtocol.close` to terminate the connection. + +.. _debugging: + +Debugging +--------- + +If you don't understand what ``websockets`` is doing, enable logging:: + + import logging + logger = logging.getLogger('websockets') + logger.setLevel(logging.INFO) + logger.addHandler(logging.StreamHandler()) + +The logs contain: + +* Exceptions in the connection handler at the ``ERROR`` level +* Exceptions in the opening or closing handshake at the ``INFO`` level +* All frames at the ``DEBUG`` level — this can be very verbose + +If you're new to ``asyncio``, you will certainly encounter issues that are +related to asynchronous programming in general rather than to ``websockets`` +in particular. Fortunately Python's official documentation provides advice to +`develop with asyncio`_. Check it out: it's invaluable! + +.. _develop with asyncio: https://docs.python.org/3/library/asyncio-dev.html + +Passing additional arguments to the connection handler +------------------------------------------------------ + +When writing a server, if you need to pass additional arguments to the +connection handler, you can bind them with :func:`functools.partial`:: + + import asyncio + import functools + import websockets + + async def handler(websocket, path, extra_argument): + ... + + bound_handler = functools.partial(handler, extra_argument='spam') + start_server = websockets.serve(bound_handler, '127.0.0.1', 8765) + + asyncio.get_event_loop().run_until_complete(start_server) + asyncio.get_event_loop().run_forever() + +Another way to achieve this result is to define the ``handler`` coroutine in +a scope where the ``extra_argument`` variable exists instead of injecting it +through an argument. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/conf.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/conf.py new file mode 100644 index 00000000000..064c657bf16 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/conf.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +# +# websockets documentation build configuration file, created by +# sphinx-quickstart on Sun Mar 31 20:48:44 2013. +# +# 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, os, datetime + +# 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.join(os.path.abspath('..'), 'src')) + +# -- 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', + 'sphinx_autodoc_typehints', + 'sphinxcontrib_trio', + ] + +# Spelling check needs an additional module that is not installed by default. +# Add it only if spelling check is requested so docs can be generated without it. +if 'spelling' in sys.argv: + extensions.append('sphinxcontrib.spelling') + +# 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 = 'websockets' +copyright = f'2013-{datetime.date.today().year}, Aymeric Augustin and contributors' + +# 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 = '8.1' +# The full version, including alpha/beta/rc tags. +release = '8.1' + +# 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 = [] + + +# -- 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 = 'alabaster' + +# 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 = { + 'logo': 'websockets.svg', + 'description': 'A library for building WebSocket servers and clients in Python with a focus on correctness and simplicity.', + 'github_button': True, + 'github_user': 'aaugustin', + 'github_repo': 'websockets', + 'tidelift_url': 'https://tidelift.com/subscription/pkg/pypi-websockets?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=docs', +} + +# 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'] + +# 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 = { + '**': [ + 'about.html', + 'searchbox.html', + 'navigation.html', + 'relations.html', + 'donate.html', + ] +} + +# 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 = 'websocketsdoc' + + +# -- 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]). +latex_documents = [ + ('index', 'websockets.tex', 'websockets Documentation', + 'Aymeric Augustin', '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', 'websockets', 'websockets Documentation', + ['Aymeric Augustin'], 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', 'websockets', 'websockets Documentation', + 'Aymeric Augustin', 'websockets', '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' + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/3/': None} diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/contributing.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/contributing.rst new file mode 100644 index 00000000000..40f1dbb54ae --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/contributing.rst @@ -0,0 +1,61 @@ +Contributing +============ + +Thanks for taking the time to contribute to websockets! + +Code of Conduct +--------------- + +This project and everyone participating in it is governed by the `Code of +Conduct`_. By participating, you are expected to uphold this code. Please +report inappropriate behavior to aymeric DOT augustin AT fractalideas DOT com. + +.. _Code of Conduct: https://github.com/aaugustin/websockets/blob/master/CODE_OF_CONDUCT.md + +*(If I'm the person with the inappropriate behavior, please accept my +apologies. I know I can mess up. I can't expect you to tell me, but if you +choose to do so, I'll do my best to handle criticism constructively. +-- Aymeric)* + +Contributions +------------- + +Bug reports, patches and suggestions are welcome! + +Please open an issue_ or send a `pull request`_. + +Feedback about the documentation is especially valuable — the authors of +``websockets`` feel more confident about writing code than writing docs :-) + +If you're wondering why things are done in a certain way, the :doc:`design +document <design>` provides lots of details about the internals of websockets. + +.. _issue: https://github.com/aaugustin/websockets/issues/new +.. _pull request: https://github.com/aaugustin/websockets/compare/ + +Questions +--------- + +GitHub issues aren't a good medium for handling questions. There are better +places to ask questions, for example Stack Overflow. + +If you want to ask a question anyway, please make sure that: + +- it's a question about ``websockets`` and not about :mod:`asyncio`; +- it isn't answered by the documentation; +- it wasn't asked already. + +A good question can be written as a suggestion to improve the documentation. + +Bitcoin users +------------- + +websockets appears to be quite popular for interfacing with Bitcoin or other +cryptocurrency trackers. I'm strongly opposed to Bitcoin's carbon footprint. + +Please stop heating the planet where my children are supposed to live, thanks. + +Since ``websockets`` is released under an open-source license, you can use it +for any purpose you like. However, I won't spend any of my time to help. + +I will summarily close issues related to Bitcoin or cryptocurrency in any way. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/deployment.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/deployment.rst new file mode 100644 index 00000000000..5b05afff143 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/deployment.rst @@ -0,0 +1,162 @@ +Deployment +========== + +.. currentmodule:: websockets + +Application server +------------------ + +The author of ``websockets`` isn't aware of best practices for deploying +network services based on :mod:`asyncio`, let alone application servers. + +You can run a script similar to the :ref:`server example <server-example>`, +inside a supervisor if you deem that useful. + +You can also add a wrapper to daemonize the process. Third-party libraries +provide solutions for that. + +If you can share knowledge on this topic, please file an issue_. Thanks! + +.. _issue: https://github.com/aaugustin/websockets/issues/new + +Graceful shutdown +----------------- + +You may want to close connections gracefully when shutting down the server, +perhaps after executing some cleanup logic. There are two ways to achieve this +with the object returned by :func:`~server.serve`: + +- using it as a asynchronous context manager, or +- calling its ``close()`` method, then waiting for its ``wait_closed()`` + method to complete. + +On Unix systems, shutdown is usually triggered by sending a signal. + +Here's a full example for handling SIGTERM on Unix: + +.. literalinclude:: ../example/shutdown.py + :emphasize-lines: 13,17-19 + +This example is easily adapted to handle other signals. If you override the +default handler for SIGINT, which raises :exc:`KeyboardInterrupt`, be aware +that you won't be able to interrupt a program with Ctrl-C anymore when it's +stuck in a loop. + +It's more difficult to achieve the same effect on Windows. Some third-party +projects try to help with this problem. + +If your server doesn't run in the main thread, look at +:func:`~asyncio.AbstractEventLoop.call_soon_threadsafe`. + +Memory usage +------------ + +.. _memory-usage: + +In most cases, memory usage of a WebSocket server is proportional to the +number of open connections. When a server handles thousands of connections, +memory usage can become a bottleneck. + +Memory usage of a single connection is the sum of: + +1. the baseline amount of memory ``websockets`` requires for each connection, +2. the amount of data held in buffers before the application processes it, +3. any additional memory allocated by the application itself. + +Baseline +........ + +Compression settings are the main factor affecting the baseline amount of +memory used by each connection. + +By default ``websockets`` maximizes compression rate at the expense of memory +usage. If memory usage is an issue, lowering compression settings can help: + +- Context Takeover is necessary to get good performance for almost all + applications. It should remain enabled. +- Window Bits is a trade-off between memory usage and compression rate. + It defaults to 15 and can be lowered. The default value isn't optimal + for small, repetitive messages which are typical of WebSocket servers. +- Memory Level is a trade-off between memory usage and compression speed. + It defaults to 8 and can be lowered. A lower memory level can actually + increase speed thanks to memory locality, even if the CPU does more work! + +See this :ref:`example <per-message-deflate-configuration-example>` for how to +configure compression settings. + +Here's how various compression settings affect memory usage of a single +connection on a 64-bit system, as well a benchmark_ of compressed size and +compression time for a corpus of small JSON documents. + ++-------------+-------------+--------------+--------------+------------------+------------------+ +| Compression | Window Bits | Memory Level | Memory usage | Size vs. default | Time vs. default | ++=============+=============+==============+==============+==================+==================+ +| *default* | 15 | 8 | 325 KiB | +0% | +0% + ++-------------+-------------+--------------+--------------+------------------+------------------+ +| | 14 | 7 | 181 KiB | +1.5% | -5.3% | ++-------------+-------------+--------------+--------------+------------------+------------------+ +| | 13 | 6 | 110 KiB | +2.8% | -7.5% | ++-------------+-------------+--------------+--------------+------------------+------------------+ +| | 12 | 5 | 73 KiB | +4.4% | -18.9% | ++-------------+-------------+--------------+--------------+------------------+------------------+ +| | 11 | 4 | 55 KiB | +8.5% | -18.8% | ++-------------+-------------+--------------+--------------+------------------+------------------+ +| *disabled* | N/A | N/A | 22 KiB | N/A | N/A | ++-------------+-------------+--------------+--------------+------------------+------------------+ + +*Don't assume this example is representative! Compressed size and compression +time depend heavily on the kind of messages exchanged by the application!* + +You can run the same benchmark for your application by creating a list of +typical messages and passing it to the ``_benchmark`` function_. + +.. _benchmark: https://gist.github.com/aaugustin/fbea09ce8b5b30c4e56458eb081fe599 +.. _function: https://gist.github.com/aaugustin/fbea09ce8b5b30c4e56458eb081fe599#file-compression-py-L48-L144 + +This `blog post by Ilya Grigorik`_ provides more details about how compression +settings affect memory usage and how to optimize them. + +.. _blog post by Ilya Grigorik: https://www.igvita.com/2013/11/27/configuring-and-optimizing-websocket-compression/ + +This `experiment by Peter Thorson`_ suggests Window Bits = 11, Memory Level = +4 as a sweet spot for optimizing memory usage. + +.. _experiment by Peter Thorson: https://www.ietf.org/mail-archive/web/hybi/current/msg10222.html + +Buffers +....... + +Under normal circumstances, buffers are almost always empty. + +Under high load, if a server receives more messages than it can process, +bufferbloat can result in excessive memory use. + +By default ``websockets`` has generous limits. It is strongly recommended to +adapt them to your application. When you call :func:`~server.serve`: + +- Set ``max_size`` (default: 1 MiB, UTF-8 encoded) to the maximum size of + messages your application generates. +- Set ``max_queue`` (default: 32) to the maximum number of messages your + application expects to receive faster than it can process them. The queue + provides burst tolerance without slowing down the TCP connection. + +Furthermore, you can lower ``read_limit`` and ``write_limit`` (default: +64 KiB) to reduce the size of buffers for incoming and outgoing data. + +The design document provides :ref:`more details about buffers<buffers>`. + +Port sharing +------------ + +The WebSocket protocol is an extension of HTTP/1.1. It can be tempting to +serve both HTTP and WebSocket on the same port. + +The author of ``websockets`` doesn't think that's a good idea, due to the +widely different operational characteristics of HTTP and WebSocket. + +``websockets`` provide minimal support for responding to HTTP requests with +the :meth:`~server.WebSocketServerProtocol.process_request` hook. Typical +use cases include health checks. Here's an example: + +.. literalinclude:: ../example/health_check_server.py + :emphasize-lines: 9-11,17-19 diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/design.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/design.rst new file mode 100644 index 00000000000..74279b87f6e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/design.rst @@ -0,0 +1,571 @@ +Design +====== + +.. currentmodule:: websockets + +This document describes the design of ``websockets``. It assumes familiarity +with the specification of the WebSocket protocol in :rfc:`6455`. + +It's primarily intended at maintainers. It may also be useful for users who +wish to understand what happens under the hood. + +.. warning:: + + Internals described in this document may change at any time. + + Backwards compatibility is only guaranteed for `public APIs <api>`_. + + +Lifecycle +--------- + +State +..... + +WebSocket connections go through a trivial state machine: + +- ``CONNECTING``: initial state, +- ``OPEN``: when the opening handshake is complete, +- ``CLOSING``: when the closing handshake is started, +- ``CLOSED``: when the TCP connection is closed. + +Transitions happen in the following places: + +- ``CONNECTING -> OPEN``: in + :meth:`~protocol.WebSocketCommonProtocol.connection_open` which runs when + the :ref:`opening handshake <opening-handshake>` completes and the WebSocket + connection is established — not to be confused with + :meth:`~asyncio.Protocol.connection_made` which runs when the TCP connection + is established; +- ``OPEN -> CLOSING``: in + :meth:`~protocol.WebSocketCommonProtocol.write_frame` immediately before + sending a close frame; since receiving a close frame triggers sending a + close frame, this does the right thing regardless of which side started the + :ref:`closing handshake <closing-handshake>`; also in + :meth:`~protocol.WebSocketCommonProtocol.fail_connection` which duplicates + a few lines of code from ``write_close_frame()`` and ``write_frame()``; +- ``* -> CLOSED``: in + :meth:`~protocol.WebSocketCommonProtocol.connection_lost` which is always + called exactly once when the TCP connection is closed. + +Coroutines +.......... + +The following diagram shows which coroutines are running at each stage of the +connection lifecycle on the client side. + +.. image:: lifecycle.svg + :target: _images/lifecycle.svg + +The lifecycle is identical on the server side, except inversion of control +makes the equivalent of :meth:`~client.connect` implicit. + +Coroutines shown in green are called by the application. Multiple coroutines +may interact with the WebSocket connection concurrently. + +Coroutines shown in gray manage the connection. When the opening handshake +succeeds, :meth:`~protocol.WebSocketCommonProtocol.connection_open` starts +two tasks: + +- :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` runs + :meth:`~protocol.WebSocketCommonProtocol.transfer_data` which handles + incoming data and lets :meth:`~protocol.WebSocketCommonProtocol.recv` + consume it. It may be canceled to terminate the connection. It never exits + with an exception other than :exc:`~asyncio.CancelledError`. See :ref:`data + transfer <data-transfer>` below. + +- :attr:`~protocol.WebSocketCommonProtocol.keepalive_ping_task` runs + :meth:`~protocol.WebSocketCommonProtocol.keepalive_ping` which sends Ping + frames at regular intervals and ensures that corresponding Pong frames are + received. It is canceled when the connection terminates. It never exits + with an exception other than :exc:`~asyncio.CancelledError`. + +- :attr:`~protocol.WebSocketCommonProtocol.close_connection_task` runs + :meth:`~protocol.WebSocketCommonProtocol.close_connection` which waits for + the data transfer to terminate, then takes care of closing the TCP + connection. It must not be canceled. It never exits with an exception. See + :ref:`connection termination <connection-termination>` below. + +Besides, :meth:`~protocol.WebSocketCommonProtocol.fail_connection` starts +the same :attr:`~protocol.WebSocketCommonProtocol.close_connection_task` when +the opening handshake fails, in order to close the TCP connection. + +Splitting the responsibilities between two tasks makes it easier to guarantee +that ``websockets`` can terminate connections: + +- within a fixed timeout, +- without leaking pending tasks, +- without leaking open TCP connections, + +regardless of whether the connection terminates normally or abnormally. + +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` completes when no +more data will be received on the connection. Under normal circumstances, it +exits after exchanging close frames. + +:attr:`~protocol.WebSocketCommonProtocol.close_connection_task` completes when +the TCP connection is closed. + + +.. _opening-handshake: + +Opening handshake +----------------- + +``websockets`` performs the opening handshake when establishing a WebSocket +connection. On the client side, :meth:`~client.connect` executes it before +returning the protocol to the caller. On the server side, it's executed before +passing the protocol to the ``ws_handler`` coroutine handling the connection. + +While the opening handshake is asymmetrical — the client sends an HTTP Upgrade +request and the server replies with an HTTP Switching Protocols response — +``websockets`` aims at keeping the implementation of both sides consistent +with one another. + +On the client side, :meth:`~client.WebSocketClientProtocol.handshake`: + +- builds a HTTP request based on the ``uri`` and parameters passed to + :meth:`~client.connect`; +- writes the HTTP request to the network; +- reads a HTTP response from the network; +- checks the HTTP response, validates ``extensions`` and ``subprotocol``, and + configures the protocol accordingly; +- moves to the ``OPEN`` state. + +On the server side, :meth:`~server.WebSocketServerProtocol.handshake`: + +- reads a HTTP request from the network; +- calls :meth:`~server.WebSocketServerProtocol.process_request` which may + abort the WebSocket handshake and return a HTTP response instead; this + hook only makes sense on the server side; +- checks the HTTP request, negotiates ``extensions`` and ``subprotocol``, and + configures the protocol accordingly; +- builds a HTTP response based on the above and parameters passed to + :meth:`~server.serve`; +- writes the HTTP response to the network; +- moves to the ``OPEN`` state; +- returns the ``path`` part of the ``uri``. + +The most significant asymmetry between the two sides of the opening handshake +lies in the negotiation of extensions and, to a lesser extent, of the +subprotocol. The server knows everything about both sides and decides what the +parameters should be for the connection. The client merely applies them. + +If anything goes wrong during the opening handshake, ``websockets`` +:ref:`fails the connection <connection-failure>`. + + +.. _data-transfer: + +Data transfer +------------- + +Symmetry +........ + +Once the opening handshake has completed, the WebSocket protocol enters the +data transfer phase. This part is almost symmetrical. There are only two +differences between a server and a client: + +- `client-to-server masking`_: the client masks outgoing frames; the server + unmasks incoming frames; +- `closing the TCP connection`_: the server closes the connection immediately; + the client waits for the server to do it. + +.. _client-to-server masking: https://tools.ietf.org/html/rfc6455#section-5.3 +.. _closing the TCP connection: https://tools.ietf.org/html/rfc6455#section-5.5.1 + +These differences are so minor that all the logic for `data framing`_, for +`sending and receiving data`_ and for `closing the connection`_ is implemented +in the same class, :class:`~protocol.WebSocketCommonProtocol`. + +.. _data framing: https://tools.ietf.org/html/rfc6455#section-5 +.. _sending and receiving data: https://tools.ietf.org/html/rfc6455#section-6 +.. _closing the connection: https://tools.ietf.org/html/rfc6455#section-7 + +The :attr:`~protocol.WebSocketCommonProtocol.is_client` attribute tells which +side a protocol instance is managing. This attribute is defined on the +:attr:`~server.WebSocketServerProtocol` and +:attr:`~client.WebSocketClientProtocol` classes. + +Data flow +......... + +The following diagram shows how data flows between an application built on top +of ``websockets`` and a remote endpoint. It applies regardless of which side +is the server or the client. + +.. image:: protocol.svg + :target: _images/protocol.svg + +Public methods are shown in green, private methods in yellow, and buffers in +orange. Methods related to connection termination are omitted; connection +termination is discussed in another section below. + +Receiving data +.............. + +The left side of the diagram shows how ``websockets`` receives data. + +Incoming data is written to a :class:`~asyncio.StreamReader` in order to +implement flow control and provide backpressure on the TCP connection. + +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task`, which is started +when the WebSocket connection is established, processes this data. + +When it receives data frames, it reassembles fragments and puts the resulting +messages in the :attr:`~protocol.WebSocketCommonProtocol.messages` queue. + +When it encounters a control frame: + +- if it's a close frame, it starts the closing handshake; +- if it's a ping frame, it answers with a pong frame; +- if it's a pong frame, it acknowledges the corresponding ping (unless it's an + unsolicited pong). + +Running this process in a task guarantees that control frames are processed +promptly. Without such a task, ``websockets`` would depend on the application +to drive the connection by having exactly one coroutine awaiting +:meth:`~protocol.WebSocketCommonProtocol.recv` at any time. While this +happens naturally in many use cases, it cannot be relied upon. + +Then :meth:`~protocol.WebSocketCommonProtocol.recv` fetches the next message +from the :attr:`~protocol.WebSocketCommonProtocol.messages` queue, with some +complexity added for handling backpressure and termination correctly. + +Sending data +............ + +The right side of the diagram shows how ``websockets`` sends data. + +:meth:`~protocol.WebSocketCommonProtocol.send` writes one or several data +frames containing the message. While sending a fragmented message, concurrent +calls to :meth:`~protocol.WebSocketCommonProtocol.send` are put on hold until +all fragments are sent. This makes concurrent calls safe. + +:meth:`~protocol.WebSocketCommonProtocol.ping` writes a ping frame and +yields a :class:`~asyncio.Future` which will be completed when a matching pong +frame is received. + +:meth:`~protocol.WebSocketCommonProtocol.pong` writes a pong frame. + +:meth:`~protocol.WebSocketCommonProtocol.close` writes a close frame and +waits for the TCP connection to terminate. + +Outgoing data is written to a :class:`~asyncio.StreamWriter` in order to +implement flow control and provide backpressure from the TCP connection. + +.. _closing-handshake: + +Closing handshake +................. + +When the other side of the connection initiates the closing handshake, +:meth:`~protocol.WebSocketCommonProtocol.read_message` receives a close +frame while in the ``OPEN`` state. It moves to the ``CLOSING`` state, sends a +close frame, and returns ``None``, causing +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` to terminate. + +When this side of the connection initiates the closing handshake with +:meth:`~protocol.WebSocketCommonProtocol.close`, it moves to the ``CLOSING`` +state and sends a close frame. When the other side sends a close frame, +:meth:`~protocol.WebSocketCommonProtocol.read_message` receives it in the +``CLOSING`` state and returns ``None``, also causing +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` to terminate. + +If the other side doesn't send a close frame within the connection's close +timeout, ``websockets`` :ref:`fails the connection <connection-failure>`. + +The closing handshake can take up to ``2 * close_timeout``: one +``close_timeout`` to write a close frame and one ``close_timeout`` to receive +a close frame. + +Then ``websockets`` terminates the TCP connection. + + +.. _connection-termination: + +Connection termination +---------------------- + +:attr:`~protocol.WebSocketCommonProtocol.close_connection_task`, which is +started when the WebSocket connection is established, is responsible for +eventually closing the TCP connection. + +First :attr:`~protocol.WebSocketCommonProtocol.close_connection_task` waits +for :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` to terminate, +which may happen as a result of: + +- a successful closing handshake: as explained above, this exits the infinite + loop in :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task`; +- a timeout while waiting for the closing handshake to complete: this cancels + :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task`; +- a protocol error, including connection errors: depending on the exception, + :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` :ref:`fails the + connection <connection-failure>` with a suitable code and exits. + +:attr:`~protocol.WebSocketCommonProtocol.close_connection_task` is separate +from :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` to make it +easier to implement the timeout on the closing handshake. Canceling +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` creates no risk +of canceling :attr:`~protocol.WebSocketCommonProtocol.close_connection_task` +and failing to close the TCP connection, thus leaking resources. + +Then :attr:`~protocol.WebSocketCommonProtocol.close_connection_task` cancels +:attr:`~protocol.WebSocketCommonProtocol.keepalive_ping`. This task has no +protocol compliance responsibilities. Terminating it to avoid leaking it is +the only concern. + +Terminating the TCP connection can take up to ``2 * close_timeout`` on the +server side and ``3 * close_timeout`` on the client side. Clients start by +waiting for the server to close the connection, hence the extra +``close_timeout``. Then both sides go through the following steps until the +TCP connection is lost: half-closing the connection (only for non-TLS +connections), closing the connection, aborting the connection. At this point +the connection drops regardless of what happens on the network. + + +.. _connection-failure: + +Connection failure +------------------ + +If the opening handshake doesn't complete successfully, ``websockets`` fails +the connection by closing the TCP connection. + +Once the opening handshake has completed, ``websockets`` fails the connection +by canceling :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` and +sending a close frame if appropriate. + +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` exits, unblocking +:attr:`~protocol.WebSocketCommonProtocol.close_connection_task`, which closes +the TCP connection. + + +.. _server-shutdown: + +Server shutdown +--------------- + +:class:`~websockets.server.WebSocketServer` closes asynchronously like +:class:`asyncio.Server`. The shutdown happen in two steps: + +1. Stop listening and accepting new connections; +2. Close established connections with close code 1001 (going away) or, if + the opening handshake is still in progress, with HTTP status code 503 + (Service Unavailable). + +The first call to :class:`~websockets.server.WebSocketServer.close` starts a +task that performs this sequence. Further calls are ignored. This is the +easiest way to make :class:`~websockets.server.WebSocketServer.close` and +:class:`~websockets.server.WebSocketServer.wait_closed` idempotent. + + +.. _cancellation: + +Cancellation +------------ + +User code +......... + +``websockets`` provides a WebSocket application server. It manages connections +and passes them to user-provided connection handlers. This is an *inversion of +control* scenario: library code calls user code. + +If a connection drops, the corresponding handler should terminate. If the +server shuts down, all connection handlers must terminate. Canceling +connection handlers would terminate them. + +However, using cancellation for this purpose would require all connection +handlers to handle it properly. For example, if a connection handler starts +some tasks, it should catch :exc:`~asyncio.CancelledError`, terminate or +cancel these tasks, and then re-raise the exception. + +Cancellation is tricky in :mod:`asyncio` applications, especially when it +interacts with finalization logic. In the example above, what if a handler +gets interrupted with :exc:`~asyncio.CancelledError` while it's finalizing +the tasks it started, after detecting that the connection dropped? + +``websockets`` considers that cancellation may only be triggered by the caller +of a coroutine when it doesn't care about the results of that coroutine +anymore. (Source: `Guido van Rossum <https://groups.google.com/forum/#!msg +/python-tulip/LZQe38CR3bg/7qZ1p_q5yycJ>`_). Since connection handlers run +arbitrary user code, ``websockets`` has no way of deciding whether that code +is still doing something worth caring about. + +For these reasons, ``websockets`` never cancels connection handlers. Instead +it expects them to detect when the connection is closed, execute finalization +logic if needed, and exit. + +Conversely, cancellation isn't a concern for WebSocket clients because they +don't involve inversion of control. + +Library +....... + +Most :doc:`public APIs <api>` of ``websockets`` are coroutines. They may be +canceled, for example if the user starts a task that calls these coroutines +and cancels the task later. ``websockets`` must handle this situation. + +Cancellation during the opening handshake is handled like any other exception: +the TCP connection is closed and the exception is re-raised. This can only +happen on the client side. On the server side, the opening handshake is +managed by ``websockets`` and nothing results in a cancellation. + +Once the WebSocket connection is established, internal tasks +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` and +:attr:`~protocol.WebSocketCommonProtocol.close_connection_task` mustn't get +accidentally canceled if a coroutine that awaits them is canceled. In other +words, they must be shielded from cancellation. + +:meth:`~protocol.WebSocketCommonProtocol.recv` waits for the next message in +the queue or for :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` +to terminate, whichever comes first. It relies on :func:`~asyncio.wait` for +waiting on two futures in parallel. As a consequence, even though it's waiting +on a :class:`~asyncio.Future` signaling the next message and on +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task`, it doesn't +propagate cancellation to them. + +:meth:`~protocol.WebSocketCommonProtocol.ensure_open` is called by +:meth:`~protocol.WebSocketCommonProtocol.send`, +:meth:`~protocol.WebSocketCommonProtocol.ping`, and +:meth:`~protocol.WebSocketCommonProtocol.pong`. When the connection state is +``CLOSING``, it waits for +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` but shields it to +prevent cancellation. + +:meth:`~protocol.WebSocketCommonProtocol.close` waits for the data transfer +task to terminate with :func:`~asyncio.wait_for`. If it's canceled or if the +timeout elapses, :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` +is canceled, which is correct at this point. +:meth:`~protocol.WebSocketCommonProtocol.close` then waits for +:attr:`~protocol.WebSocketCommonProtocol.close_connection_task` but shields it +to prevent cancellation. + +:meth:`~protocol.WebSocketCommonProtocol.close` and +:func:`~protocol.WebSocketCommonProtocol.fail_connection` are the only +places where :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` may +be canceled. + +:attr:`~protocol.WebSocketCommonProtocol.close_connnection_task` starts by +waiting for :attr:`~protocol.WebSocketCommonProtocol.transfer_data_task`. It +catches :exc:`~asyncio.CancelledError` to prevent a cancellation of +:attr:`~protocol.WebSocketCommonProtocol.transfer_data_task` from propagating +to :attr:`~protocol.WebSocketCommonProtocol.close_connnection_task`. + +.. _backpressure: + +Backpressure +------------ + +.. note:: + + This section discusses backpressure from the perspective of a server but + the concept applies to clients symmetrically. + +With a naive implementation, if a server receives inputs faster than it can +process them, or if it generates outputs faster than it can send them, data +accumulates in buffers, eventually causing the server to run out of memory and +crash. + +The solution to this problem is backpressure. Any part of the server that +receives inputs faster than it can process them and send the outputs +must propagate that information back to the previous part in the chain. + +``websockets`` is designed to make it easy to get backpressure right. + +For incoming data, ``websockets`` builds upon :class:`~asyncio.StreamReader` +which propagates backpressure to its own buffer and to the TCP stream. Frames +are parsed from the input stream and added to a bounded queue. If the queue +fills up, parsing halts until the application reads a frame. + +For outgoing data, ``websockets`` builds upon :class:`~asyncio.StreamWriter` +which implements flow control. If the output buffers grow too large, it waits +until they're drained. That's why all APIs that write frames are asynchronous. + +Of course, it's still possible for an application to create its own unbounded +buffers and break the backpressure. Be careful with queues. + + +.. _buffers: + +Buffers +------- + +.. note:: + + This section discusses buffers from the perspective of a server but it + applies to clients as well. + +An asynchronous systems works best when its buffers are almost always empty. + +For example, if a client sends data too fast for a server, the queue of +incoming messages will be constantly full. The server will always be 32 +messages (by default) behind the client. This consumes memory and increases +latency for no good reason. The problem is called bufferbloat. + +If buffers are almost always full and that problem cannot be solved by adding +capacity — typically because the system is bottlenecked by the output and +constantly regulated by backpressure — reducing the size of buffers minimizes +negative consequences. + +By default ``websockets`` has rather high limits. You can decrease them +according to your application's characteristics. + +Bufferbloat can happen at every level in the stack where there is a buffer. +For each connection, the receiving side contains these buffers: + +- OS buffers: tuning them is an advanced optimization. +- :class:`~asyncio.StreamReader` bytes buffer: the default limit is 64 KiB. + You can set another limit by passing a ``read_limit`` keyword argument to + :func:`~client.connect()` or :func:`~server.serve`. +- Incoming messages :class:`~collections.deque`: its size depends both on + the size and the number of messages it contains. By default the maximum + UTF-8 encoded size is 1 MiB and the maximum number is 32. In the worst case, + after UTF-8 decoding, a single message could take up to 4 MiB of memory and + the overall memory consumption could reach 128 MiB. You should adjust these + limits by setting the ``max_size`` and ``max_queue`` keyword arguments of + :func:`~client.connect()` or :func:`~server.serve` according to your + application's requirements. + +For each connection, the sending side contains these buffers: + +- :class:`~asyncio.StreamWriter` bytes buffer: the default size is 64 KiB. + You can set another limit by passing a ``write_limit`` keyword argument to + :func:`~client.connect()` or :func:`~server.serve`. +- OS buffers: tuning them is an advanced optimization. + +Concurrency +----------- + +Awaiting any combination of :meth:`~protocol.WebSocketCommonProtocol.recv`, +:meth:`~protocol.WebSocketCommonProtocol.send`, +:meth:`~protocol.WebSocketCommonProtocol.close` +:meth:`~protocol.WebSocketCommonProtocol.ping`, or +:meth:`~protocol.WebSocketCommonProtocol.pong` concurrently is safe, including +multiple calls to the same method, with one exception and one limitation. + +* **Only one coroutine can receive messages at a time.** This constraint + avoids non-deterministic behavior (and simplifies the implementation). If a + coroutine is awaiting :meth:`~protocol.WebSocketCommonProtocol.recv`, + awaiting it again in another coroutine raises :exc:`RuntimeError`. + +* **Sending a fragmented message forces serialization.** Indeed, the WebSocket + protocol doesn't support multiplexing messages. If a coroutine is awaiting + :meth:`~protocol.WebSocketCommonProtocol.send` to send a fragmented message, + awaiting it again in another coroutine waits until the first call completes. + This will be transparent in many cases. It may be a concern if the + fragmented message is generated slowly by an asynchronous iterator. + +Receiving frames is independent from sending frames. This isolates +:meth:`~protocol.WebSocketCommonProtocol.recv`, which receives frames, from +the other methods, which send frames. + +While the connection is open, each frame is sent with a single write. Combined +with the concurrency model of :mod:`asyncio`, this enforces serialization. The +only other requirement is to prevent interleaving other data frames in the +middle of a fragmented message. + +After the connection is closed, sending a frame raises +:exc:`~websockets.exceptions.ConnectionClosed`, which is safe. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/extensions.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/extensions.rst new file mode 100644 index 00000000000..4000340906a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/extensions.rst @@ -0,0 +1,87 @@ +Extensions +========== + +.. currentmodule:: websockets + +The WebSocket protocol supports extensions_. + +At the time of writing, there's only one `registered extension`_, WebSocket +Per-Message Deflate, specified in :rfc:`7692`. + +.. _extensions: https://tools.ietf.org/html/rfc6455#section-9 +.. _registered extension: https://www.iana.org/assignments/websocket/websocket.xhtml#extension-name + +Per-Message Deflate +------------------- + +:func:`~server.serve()` and :func:`~client.connect` enable the Per-Message +Deflate extension by default. You can disable this with ``compression=None``. + +You can also configure the Per-Message Deflate extension explicitly if you +want to customize its parameters. + +.. _per-message-deflate-configuration-example: + +Here's an example on the server side:: + + import websockets + from websockets.extensions import permessage_deflate + + websockets.serve( + ..., + extensions=[ + permessage_deflate.ServerPerMessageDeflateFactory( + server_max_window_bits=11, + client_max_window_bits=11, + compress_settings={'memLevel': 4}, + ), + ], + ) + +Here's an example on the client side:: + + import websockets + from websockets.extensions import permessage_deflate + + websockets.connect( + ..., + extensions=[ + permessage_deflate.ClientPerMessageDeflateFactory( + server_max_window_bits=11, + client_max_window_bits=11, + compress_settings={'memLevel': 4}, + ), + ], + ) + +Refer to the API documentation of +:class:`~extensions.permessage_deflate.ServerPerMessageDeflateFactory` and +:class:`~extensions.permessage_deflate.ClientPerMessageDeflateFactory` for +details. + +Writing an extension +-------------------- + +During the opening handshake, WebSocket clients and servers negotiate which +extensions will be used with which parameters. Then each frame is processed by +extensions before it's sent and after it's received. + +As a consequence writing an extension requires implementing several classes: + +1. Extension Factory: it negotiates parameters and instantiates the extension. + Clients and servers require separate extension factories with distinct APIs. + +2. Extension: it decodes incoming frames and encodes outgoing frames. If the + extension is symmetrical, clients and servers can use the same class. + +``websockets`` provides abstract base classes for extension factories and +extensions. + +.. autoclass:: websockets.extensions.base.ServerExtensionFactory + :members: + +.. autoclass:: websockets.extensions.base.ClientExtensionFactory + :members: + +.. autoclass:: websockets.extensions.base.Extension + :members: diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/faq.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/faq.rst new file mode 100644 index 00000000000..cea3f535839 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/faq.rst @@ -0,0 +1,261 @@ +FAQ +=== + +.. currentmodule:: websockets + +.. note:: + + Many questions asked in :mod:`websockets`' issue tracker are actually + about :mod:`asyncio`. Python's documentation about `developing with + asyncio`_ is a good complement. + + .. _developing with asyncio: https://docs.python.org/3/library/asyncio-dev.html + +Server side +----------- + +Why does the server close the connection after processing one message? +...................................................................... + +Your connection handler exits after processing one message. Write a loop to +process multiple messages. + +For example, if your handler looks like this:: + + async def handler(websocket, path): + print(websocket.recv()) + +change it like this:: + + async def handler(websocket, path): + async for message in websocket: + print(message) + +*Don't feel bad if this happens to you — it's the most common question in +websockets' issue tracker :-)* + +Why can only one client connect at a time? +.......................................... + +Your connection handler blocks the event loop. Look for blocking calls. +Any call that may take some time must be asynchronous. + +For example, if you have:: + + async def handler(websocket, path): + time.sleep(1) + +change it to:: + + async def handler(websocket, path): + await asyncio.sleep(1) + +This is part of learning asyncio. It isn't specific to websockets. + +See also Python's documentation about `running blocking code`_. + +.. _running blocking code: https://docs.python.org/3/library/asyncio-dev.html#running-blocking-code + +How do I get access HTTP headers, for example cookies? +...................................................... + +To access HTTP headers during the WebSocket handshake, you can override +:attr:`~server.WebSocketServerProtocol.process_request`:: + + async def process_request(self, path, request_headers): + cookies = request_header["Cookie"] + +See + +Once the connection is established, they're available in +:attr:`~protocol.WebSocketServerProtocol.request_headers`:: + + async def handler(websocket, path): + cookies = websocket.request_headers["Cookie"] + +How do I get the IP address of the client connecting to my server? +.................................................................. + +It's available in :attr:`~protocol.WebSocketCommonProtocol.remote_address`:: + + async def handler(websocket, path): + remote_ip = websocket.remote_address[0] + +How do I set which IP addresses my server listens to? +..................................................... + +Look at the ``host`` argument of :meth:`~asyncio.loop.create_server`. + +:func:`serve` accepts the same arguments as +:meth:`~asyncio.loop.create_server`. + +How do I close a connection properly? +..................................... + +websockets takes care of closing the connection when the handler exits. + +How do I run a HTTP server and WebSocket server on the same port? +................................................................. + +This isn't supported. + +Providing a HTTP server is out of scope for websockets. It only aims at +providing a WebSocket server. + +There's limited support for returning HTTP responses with the +:attr:`~server.WebSocketServerProtocol.process_request` hook. +If you need more, pick a HTTP server and run it separately. + +Client side +----------- + +How do I close a connection properly? +..................................... + +The easiest is to use :func:`connect` as a context manager:: + + async with connect(...) as websocket: + ... + +How do I reconnect automatically when the connection drops? +........................................................... + +See `issue 414`_. + +.. _issue 414: https://github.com/aaugustin/websockets/issues/414 + +How do I disable TLS/SSL certificate verification? +.................................................. + +Look at the ``ssl`` argument of :meth:`~asyncio.loop.create_connection`. + +:func:`connect` accepts the same arguments as +:meth:`~asyncio.loop.create_connection`. + +Both sides +---------- + +How do I do two things in parallel? How do I integrate with another coroutine? +.............................................................................. + +You must start two tasks, which the event loop will run concurrently. You can +achieve this with :func:`asyncio.gather` or :func:`asyncio.wait`. + +This is also part of learning asyncio and not specific to websockets. + +Keep track of the tasks and make sure they terminate or you cancel them when +the connection terminates. + +How do I create channels or topics? +................................... + +websockets doesn't have built-in publish / subscribe for these use cases. + +Depending on the scale of your service, a simple in-memory implementation may +do the job or you may need an external publish / subscribe component. + +What does ``ConnectionClosedError: code = 1006`` mean? +...................................................... + +If you're seeing this traceback in the logs of a server: + +.. code-block:: pytb + + Error in connection handler + Traceback (most recent call last): + ... + asyncio.streams.IncompleteReadError: 0 bytes read on a total of 2 expected bytes + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + ... + websockets.exceptions.ConnectionClosedError: code = 1006 (connection closed abnormally [internal]), no reason + +or if a client crashes with this traceback: + +.. code-block:: pytb + + Traceback (most recent call last): + ... + ConnectionResetError: [Errno 54] Connection reset by peer + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + ... + websockets.exceptions.ConnectionClosedError: code = 1006 (connection closed abnormally [internal]), no reason + +it means that the TCP connection was lost. As a consequence, the WebSocket +connection was closed without receiving a close frame, which is abnormal. + +You can catch and handle :exc:`~exceptions.ConnectionClosed` to prevent it +from being logged. + +There are several reasons why long-lived connections may be lost: + +* End-user devices tend to lose network connectivity often and unpredictably + because they can move out of wireless network coverage, get unplugged from + a wired network, enter airplane mode, be put to sleep, etc. +* HTTP load balancers or proxies that aren't configured for long-lived + connections may terminate connections after a short amount of time, usually + 30 seconds. + +If you're facing a reproducible issue, :ref:`enable debug logs <debugging>` to +see when and how connections are closed. + +Are there ``onopen``, ``onmessage``, ``onerror``, and ``onclose`` callbacks? +............................................................................ + +No, there aren't. + +websockets provides high-level, coroutine-based APIs. Compared to callbacks, +coroutines make it easier to manage control flow in concurrent code. + +If you prefer callback-based APIs, you should use another library. + +Can I use ``websockets`` synchronously, without ``async`` / ``await``? +...................................................................... + +You can convert every asynchronous call to a synchronous call by wrapping it +in ``asyncio.get_event_loop().run_until_complete(...)``. + +If this turns out to be impractical, you should use another library. + +Miscellaneous +------------- + +How do I set a timeout on ``recv()``? +..................................... + +Use :func:`~asyncio.wait_for`:: + + await asyncio.wait_for(websocket.recv(), timeout=10) + +This technique works for most APIs, except for asynchronous context managers. +See `issue 574`_. + +.. _issue 574: https://github.com/aaugustin/websockets/issues/574 + +How do I keep idle connections open? +.................................... + +websockets sends pings at 20 seconds intervals to keep the connection open. + +In closes the connection if it doesn't get a pong within 20 seconds. + +You can adjust this behavior with ``ping_interval`` and ``ping_timeout``. + +How do I respond to pings? +.......................... + +websockets takes care of responding to pings with pongs. + +Is there a Python 2 version? +............................ + +No, there isn't. + +websockets builds upon asyncio which requires Python 3. + + diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/index.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/index.rst new file mode 100644 index 00000000000..1b2f85f0a4f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/index.rst @@ -0,0 +1,99 @@ +websockets +========== + +|pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |circleci| |codecov| + +.. |pypi-v| image:: https://img.shields.io/pypi/v/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pypi-l| image:: https://img.shields.io/pypi/l/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/websockets.svg + :target: https://pypi.python.org/pypi/websockets + +.. |circleci| image:: https://img.shields.io/circleci/project/github/aaugustin/websockets.svg + :target: https://circleci.com/gh/aaugustin/websockets + +.. |codecov| image:: https://codecov.io/gh/aaugustin/websockets/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aaugustin/websockets + +``websockets`` is a library for building WebSocket servers_ and clients_ in +Python with a focus on correctness and simplicity. + +.. _servers: https://github.com/aaugustin/websockets/blob/master/example/server.py +.. _clients: https://github.com/aaugustin/websockets/blob/master/example/client.py + +Built on top of :mod:`asyncio`, Python's standard asynchronous I/O framework, +it provides an elegant coroutine-based API. + +Here's how a client sends and receives messages: + +.. literalinclude:: ../example/hello.py + +And here's an echo server: + +.. literalinclude:: ../example/echo.py + +Do you like it? Let's dive in! + +Tutorials +--------- + +If you're new to ``websockets``, this is the place to start. + +.. toctree:: + :maxdepth: 2 + + intro + faq + +How-to guides +------------- + +These guides will help you build and deploy a ``websockets`` application. + +.. toctree:: + :maxdepth: 2 + + cheatsheet + deployment + extensions + +Reference +--------- + +Find all the details you could ask for, and then some. + +.. toctree:: + :maxdepth: 2 + + api + +Discussions +----------- + +Get a deeper understanding of how ``websockets`` is built and why. + +.. toctree:: + :maxdepth: 2 + + design + limitations + security + +Project +------- + +This is about websockets-the-project rather than websockets-the-software. + +.. toctree:: + :maxdepth: 2 + + changelog + contributing + license + For enterprise <tidelift> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/intro.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/intro.rst new file mode 100644 index 00000000000..8be700239f3 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/intro.rst @@ -0,0 +1,209 @@ +Getting started +=============== + +.. currentmodule:: websockets + +Requirements +------------ + +``websockets`` requires Python ≥ 3.6.1. + +You should use the latest version of Python if possible. If you're using an +older version, be aware that for each minor version (3.x), only the latest +bugfix release (3.x.y) is officially supported. + +Installation +------------ + +Install ``websockets`` with:: + + pip install websockets + +Basic example +------------- + +.. _server-example: + +Here's a WebSocket server example. + +It reads a name from the client, sends a greeting, and closes the connection. + +.. literalinclude:: ../example/server.py + :emphasize-lines: 8,17 + +.. _client-example: + +On the server side, ``websockets`` executes the handler coroutine ``hello`` +once for each WebSocket connection. It closes the connection when the handler +coroutine returns. + +Here's a corresponding WebSocket client example. + +.. literalinclude:: ../example/client.py + :emphasize-lines: 8,10 + +Using :func:`connect` as an asynchronous context manager ensures the +connection is closed before exiting the ``hello`` coroutine. + +.. _secure-server-example: + +Secure example +-------------- + +Secure WebSocket connections improve confidentiality and also reliability +because they reduce the risk of interference by bad proxies. + +The WSS protocol is to WS what HTTPS is to HTTP: the connection is encrypted +with Transport Layer Security (TLS) — which is often referred to as Secure +Sockets Layer (SSL). WSS requires TLS certificates like HTTPS. + +Here's how to adapt the server example to provide secure connections. See the +documentation of the :mod:`ssl` module for configuring the context securely. + +.. literalinclude:: ../example/secure_server.py + :emphasize-lines: 19,23-25 + +Here's how to adapt the client. + +.. literalinclude:: ../example/secure_client.py + :emphasize-lines: 10,15-18 + +This client needs a context because the server uses a self-signed certificate. + +A client connecting to a secure WebSocket server with a valid certificate +(i.e. signed by a CA that your Python installation trusts) can simply pass +``ssl=True`` to :func:`connect` instead of building a context. + +Browser-based example +--------------------- + +Here's an example of how to run a WebSocket server and connect from a browser. + +Run this script in a console: + +.. literalinclude:: ../example/show_time.py + +Then open this HTML file in a browser. + +.. literalinclude:: ../example/show_time.html + :language: html + +Synchronization example +----------------------- + +A WebSocket server can receive events from clients, process them to update the +application state, and synchronize the resulting state across clients. + +Here's an example where any client can increment or decrement a counter. +Updates are propagated to all connected clients. + +The concurrency model of :mod:`asyncio` guarantees that updates are +serialized. + +Run this script in a console: + +.. literalinclude:: ../example/counter.py + +Then open this HTML file in several browsers. + +.. literalinclude:: ../example/counter.html + :language: html + +Common patterns +--------------- + +You will usually want to process several messages during the lifetime of a +connection. Therefore you must write a loop. Here are the basic patterns for +building a WebSocket server. + +Consumer +........ + +For receiving messages and passing them to a ``consumer`` coroutine:: + + async def consumer_handler(websocket, path): + async for message in websocket: + await consumer(message) + +In this example, ``consumer`` represents your business logic for processing +messages received on the WebSocket connection. + +Iteration terminates when the client disconnects. + +Producer +........ + +For getting messages from a ``producer`` coroutine and sending them:: + + async def producer_handler(websocket, path): + while True: + message = await producer() + await websocket.send(message) + +In this example, ``producer`` represents your business logic for generating +messages to send on the WebSocket connection. + +:meth:`~protocol.WebSocketCommonProtocol.send` raises a +:exc:`~exceptions.ConnectionClosed` exception when the client disconnects, +which breaks out of the ``while True`` loop. + +Both +.... + +You can read and write messages on the same connection by combining the two +patterns shown above and running the two tasks in parallel:: + + async def handler(websocket, path): + consumer_task = asyncio.ensure_future( + consumer_handler(websocket, path)) + producer_task = asyncio.ensure_future( + producer_handler(websocket, path)) + done, pending = await asyncio.wait( + [consumer_task, producer_task], + return_when=asyncio.FIRST_COMPLETED, + ) + for task in pending: + task.cancel() + +Registration +............ + +As shown in the synchronization example above, if you need to maintain a list +of currently connected clients, you must register them when they connect and +unregister them when they disconnect. + +:: + + connected = set() + + async def handler(websocket, path): + # Register. + connected.add(websocket) + try: + # Implement logic here. + await asyncio.wait([ws.send("Hello!") for ws in connected]) + await asyncio.sleep(10) + finally: + # Unregister. + connected.remove(websocket) + +This simplistic example keeps track of connected clients in memory. This only +works as long as you run a single process. In a practical application, the +handler may subscribe to some channels on a message broker, for example. + +That's all! +----------- + +The design of the ``websockets`` API was driven by simplicity. + +You don't have to worry about performing the opening or the closing handshake, +answering pings, or any other behavior required by the specification. + +``websockets`` handles all this under the hood so you don't have to. + +One more thing... +----------------- + +``websockets`` provides an interactive client:: + + $ python -m websockets wss://echo.websocket.org/ diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/license.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/license.rst new file mode 100644 index 00000000000..842d3b07fc9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/license.rst @@ -0,0 +1,4 @@ +License +------- + +.. literalinclude:: ../LICENSE diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/lifecycle.graffle b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/lifecycle.graffle Binary files differnew file mode 100644 index 00000000000..a8ab7ff09f5 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/lifecycle.graffle diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/lifecycle.svg b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/lifecycle.svg new file mode 100644 index 00000000000..0a9818d2930 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/lifecycle.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="-14.3464565 112.653543 624.6929 372.69291" width="624.6929pt" height="372.69291pt" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata> Produced by OmniGraffle 6.6.2 <dc:date>2018-07-29 15:25:34 +0000</dc:date></metadata><defs><font-face font-family="Courier New" font-size="12" panose-1="2 7 6 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="100.097656" slope="0" x-height="443.35938" cap-height="591.79688" ascent="832.51953" descent="-300.29297" font-weight="bold"><font-face-src><font-face-name name="CourierNewPS-BoldMT"/></font-face-src></font-face><font-face font-family="Courier New" font-size="12" panose-1="2 7 3 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="41.015625" slope="0" x-height="422.85156" cap-height="571.28906" ascent="832.51953" descent="-300.29297" font-weight="500"><font-face-src><font-face-name name="CourierNewPSMT"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker" viewBox="-1 -4 8 8" markerWidth="8" markerHeight="8" color="black"><g><path d="M 5.8666667 0 L 0 0 M 0 -2.2 L 5.8666667 0 L 0 2.2" fill="none" stroke="currentColor" stroke-width="1"/></g></marker><font-face font-family="Verdana" font-size="12" panose-1="2 11 6 4 3 5 4 4 2 4" units-per-em="1000" underline-position="-87.890625" underline-thickness="58.59375" slope="0" x-height="545.41016" cap-height="727.0508" ascent="1005.3711" descent="-209.96094" font-weight="500"><font-face-src><font-face-name name="Verdana"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><g><title>Layer 1</title><text transform="translate(19.173228 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="1.5138254" y="10" textLength="72.01172">CONNECTING</tspan></text><text transform="translate(160.90551 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="23.117341" y="10" textLength="28.804688">OPEN</tspan></text><text transform="translate(359.3307 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="12.315583" y="10" textLength="50.408203">CLOSING</tspan></text><text transform="translate(501.063 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="15.916169" y="10" textLength="43.20703">CLOSED</tspan></text><line x1="198.4252" y1="170.07874" x2="198.4252" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><line x1="396.8504" y1="170.07874" x2="396.8504" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><line x1="538.58267" y1="170.07874" x2="538.58267" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><line x1="56.692913" y1="170.07874" x2="56.692913" y2="453.5433" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke-dasharray="1,3"/><path d="M 240.94488 240.94488 L 411.02362 240.94488 C 418.85128 240.94488 425.19685 247.29045 425.19685 255.11811 L 425.19685 255.11811 C 425.19685 262.94577 418.85128 269.29134 411.02362 269.29134 L 240.94488 269.29134 C 233.11722 269.29134 226.77165 262.94577 226.77165 255.11811 L 226.77165 255.11811 C 226.77165 247.29045 233.11722 240.94488 240.94488 240.94488 Z" fill="#dadada"/><path d="M 240.94488 240.94488 L 411.02362 240.94488 C 418.85128 240.94488 425.19685 247.29045 425.19685 255.11811 L 425.19685 255.11811 C 425.19685 262.94577 418.85128 269.29134 411.02362 269.29134 L 240.94488 269.29134 C 233.11722 269.29134 226.77165 262.94577 226.77165 255.11811 L 226.77165 255.11811 C 226.77165 247.29045 233.11722 240.94488 240.94488 240.94488 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(226.77165 248.11811)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="52.40498" y="10" textLength="93.615234">transfer_data</tspan></text><path d="M 240.94488 354.3307 L 552.7559 354.3307 C 560.58356 354.3307 566.92913 360.67628 566.92913 368.50393 L 566.92913 368.50393 C 566.92913 376.3316 560.58356 382.67716 552.7559 382.67716 L 240.94488 382.67716 C 233.11722 382.67716 226.77165 376.3316 226.77165 368.50393 L 226.77165 368.50393 C 226.77165 360.67628 233.11722 354.3307 240.94488 354.3307 Z" fill="#dadada"/><path d="M 240.94488 354.3307 L 552.7559 354.3307 C 560.58356 354.3307 566.92913 360.67628 566.92913 368.50393 L 566.92913 368.50393 C 566.92913 376.3316 560.58356 382.67716 552.7559 382.67716 L 240.94488 382.67716 C 233.11722 382.67716 226.77165 376.3316 226.77165 368.50393 L 226.77165 368.50393 C 226.77165 360.67628 233.11722 354.3307 240.94488 354.3307 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(231.77165 361.50393)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="107.469364" y="10" textLength="115.21875">close_connection</tspan></text><path d="M 99.2126 184.25197 L 155.90551 184.25197 C 163.73317 184.25197 170.07874 190.59754 170.07874 198.4252 L 170.07874 198.4252 C 170.07874 206.25285 163.73317 212.59842 155.90551 212.59842 L 99.2126 212.59842 C 91.38494 212.59842 85.03937 206.25285 85.03937 198.4252 L 85.03937 198.4252 C 85.03937 190.59754 91.38494 184.25197 99.2126 184.25197 Z" fill="#6f6"/><path d="M 99.2126 184.25197 L 155.90551 184.25197 C 163.73317 184.25197 170.07874 190.59754 170.07874 198.4252 L 170.07874 198.4252 C 170.07874 206.25285 163.73317 212.59842 155.90551 212.59842 L 99.2126 212.59842 C 91.38494 212.59842 85.03937 206.25285 85.03937 198.4252 L 85.03937 198.4252 C 85.03937 190.59754 91.38494 184.25197 99.2126 184.25197 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 191.4252)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="12.315583" y="10" textLength="50.408203">connect</tspan></text><path d="M 240.94488 184.25197 L 496.063 184.25197 C 503.89065 184.25197 510.23622 190.59754 510.23622 198.4252 L 510.23622 198.4252 C 510.23622 206.25285 503.89065 212.59842 496.063 212.59842 L 240.94488 212.59842 C 233.11722 212.59842 226.77165 206.25285 226.77165 198.4252 L 226.77165 198.4252 C 226.77165 190.59754 233.11722 184.25197 240.94488 184.25197 Z" fill="#6f6"/><path d="M 240.94488 184.25197 L 496.063 184.25197 C 503.89065 184.25197 510.23622 190.59754 510.23622 198.4252 L 510.23622 198.4252 C 510.23622 206.25285 503.89065 212.59842 496.063 212.59842 L 240.94488 212.59842 C 233.11722 212.59842 226.77165 206.25285 226.77165 198.4252 L 226.77165 198.4252 C 226.77165 190.59754 233.11722 184.25197 240.94488 184.25197 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(231.77165 191.4252)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="17.912947" y="10" textLength="100.816406">recv / send / </tspan><tspan font-family="Courier New" font-size="12" font-weight="500" x="118.72935" y="10" textLength="93.615234">ping / pong /</tspan><tspan font-family="Courier New" font-size="12" font-weight="bold" x="212.34459" y="10" textLength="50.408203"> close </tspan></text><path d="M 170.07874 198.4252 L 183.97874 198.4252 L 198.4252 198.4252 L 198.4252 283.46457 L 198.4252 368.50393 L 212.87165 368.50393 L 215.37165 368.50393" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(75.86614 410.19685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="27.760296" y="12" textLength="52.083984">opening </tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="19.164593" y="27" textLength="58.02539">handshak</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="77.072796" y="27" textLength="7.1484375">e</tspan></text><text transform="translate(416.02362 410.19685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="19.182171" y="12" textLength="65.021484">connection</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="16.861858" y="27" textLength="69.66211">termination</tspan></text><text transform="translate(217.59842 410.19685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="41.03058" y="12" textLength="40.6875">data tr</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="81.507143" y="12" textLength="37.541016">ansfer</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="18.211245" y="27" textLength="116.625">& closing handshak</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="134.71906" y="27" textLength="7.1484375">e</tspan></text><path d="M 425.19685 255.11811 L 439.09685 255.11811 L 453.5433 255.11811 L 453.5433 342.9307" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 240.94488 297.6378 L 411.02362 297.6378 C 418.85128 297.6378 425.19685 303.98336 425.19685 311.81102 L 425.19685 311.81102 C 425.19685 319.63868 418.85128 325.98425 411.02362 325.98425 L 240.94488 325.98425 C 233.11722 325.98425 226.77165 319.63868 226.77165 311.81102 L 226.77165 311.81102 C 226.77165 303.98336 233.11722 297.6378 240.94488 297.6378 Z" fill="#dadada"/><path d="M 240.94488 297.6378 L 411.02362 297.6378 C 418.85128 297.6378 425.19685 303.98336 425.19685 311.81102 L 425.19685 311.81102 C 425.19685 319.63868 418.85128 325.98425 411.02362 325.98425 L 240.94488 325.98425 C 233.11722 325.98425 226.77165 319.63868 226.77165 311.81102 L 226.77165 311.81102 C 226.77165 303.98336 233.11722 297.6378 240.94488 297.6378 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(226.77165 304.81102)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="48.804395" y="10" textLength="100.816406">keepalive_ping</tspan></text><line x1="198.4252" y1="255.11811" x2="214.62165" y2="255.11811" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="198.4252" y1="311.81102" x2="215.37165" y2="311.81102" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/></g></g></svg> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/limitations.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/limitations.rst new file mode 100644 index 00000000000..bd6d32b2f63 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/limitations.rst @@ -0,0 +1,10 @@ +Limitations +----------- + +The client doesn't attempt to guarantee that there is no more than one +connection to a given IP address in a CONNECTING state. + +The client doesn't support connecting through a proxy. + +There is no way to fragment outgoing messages. A message is always sent in a +single frame. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/protocol.graffle b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/protocol.graffle Binary files differnew file mode 100644 index 00000000000..df76f49607e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/protocol.graffle diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/protocol.svg b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/protocol.svg new file mode 100644 index 00000000000..51bfd982be7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/protocol.svg @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 624.34646 822.34646" width="624.34646pt" height="822.34646pt" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata> Produced by OmniGraffle 6.6.2 <dc:date>2019-07-07 08:38:24 +0000</dc:date></metadata><defs><font-face font-family="Verdana" font-size="12" panose-1="2 11 6 4 3 5 4 4 2 4" units-per-em="1000" underline-position="-87.890625" underline-thickness="58.59375" slope="0" x-height="545.41016" cap-height="727.0508" ascent="1005.3711" descent="-209.96094" font-weight="500"><font-face-src><font-face-name name="Verdana"/></font-face-src></font-face><font-face font-family="Courier New" font-size="12" panose-1="2 7 3 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="41.015625" slope="0" x-height="422.85156" cap-height="571.28906" ascent="832.51953" descent="-300.29297" font-weight="500"><font-face-src><font-face-name name="CourierNewPSMT"/></font-face-src></font-face><font-face font-family="Courier New" font-size="12" panose-1="2 7 6 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="100.097656" slope="0" x-height="443.35938" cap-height="591.79688" ascent="832.51953" descent="-300.29297" font-weight="bold"><font-face-src><font-face-name name="CourierNewPS-BoldMT"/></font-face-src></font-face><font-face font-family="Courier New" font-size="10" panose-1="2 7 3 9 2 2 5 2 4 4" units-per-em="1000" underline-position="-232.91016" underline-thickness="41.015625" slope="0" x-height="422.85156" cap-height="571.28906" ascent="832.51953" descent="-300.29297" font-weight="500"><font-face-src><font-face-name name="CourierNewPSMT"/></font-face-src></font-face><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker" viewBox="-1 -4 8 8" markerWidth="8" markerHeight="8" color="black"><g><path d="M 5.8666667 0 L 0 0 M 0 -2.2 L 5.8666667 0 L 0 2.2" fill="none" stroke="currentColor" stroke-width="1"/></g></marker><radialGradient cx="0" cy="0" r="1" id="Gradient" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="white"/><stop offset="1" stop-color="#a5a5a5"/></radialGradient><radialGradient id="Obj_Gradient" xl:href="#Gradient" gradientTransform="translate(311.81102 708.6614) scale(145.75703)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker_2" viewBox="-1 -6 14 12" markerWidth="14" markerHeight="12" color="black"><g><path d="M 12 0 L 0 0 M 0 -4.5 L 12 0 L 0 4.5" fill="none" stroke="currentColor" stroke-width="1"/></g></marker><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="StickArrow_Marker_3" viewBox="-1 -4 8 8" markerWidth="8" markerHeight="8" color="black"><g><path d="M 5.9253333 0 L 0 0 M 0 -2.222 L 5.9253333 0 L 0 2.222" fill="none" stroke="currentColor" stroke-width="1"/></g></marker></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Canvas 1</title><rect fill="white" width="1314" height="1698"/><g><title>Layer 1</title><rect x="28.346457" y="765.35433" width="566.92913" height="28.346457" fill="#6cf"/><rect x="28.346457" y="765.35433" width="566.92913" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(33.346457 772.02755)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="228.50753" y="12" textLength="99.91406">remote endpoint</tspan></text><rect x="28.346457" y="85.03937" width="566.92913" height="566.92913" fill="white"/><rect x="28.346457" y="85.03937" width="566.92913" height="566.92913" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(33.346457 90.03937)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="243.79171" y="12" textLength="51.333984">websock</tspan><tspan font-family="Verdana" font-size="12" font-weight="500" x="295.00851" y="12" textLength="18.128906">ets</tspan><tspan font-family="Courier New" font-size="12" font-weight="500" x="195.65109" y="25" textLength="165.62695">WebSocketCommonProtocol</tspan></text><rect x="28.346457" y="28.346457" width="566.92913" height="28.346457" fill="#6f6"/><rect x="28.346457" y="28.346457" width="566.92913" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(33.346457 35.019685)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="230.0046" y="12" textLength="96.91992">application logic</tspan></text><path d="M 102.047243 586.77165 L 238.11023 586.77165 C 247.49858 586.77165 255.11811 596.93102 255.11811 609.4488 C 255.11811 621.9666 247.49858 632.12598 238.11023 632.12598 L 102.047243 632.12598 C 92.658897 632.12598 85.03937 621.9666 85.03937 609.4488 C 85.03937 596.93102 92.658897 586.77165 102.047243 586.77165" fill="#fc6"/><path d="M 102.047243 586.77165 L 238.11023 586.77165 C 247.49858 586.77165 255.11811 596.93102 255.11811 609.4488 C 255.11811 621.9666 247.49858 632.12598 238.11023 632.12598 L 102.047243 632.12598 C 92.658897 632.12598 85.03937 621.9666 85.03937 609.4488 C 85.03937 596.93102 92.658897 586.77165 102.047243 586.77165 M 238.11023 586.77165 C 228.72189 586.77165 221.10236 596.93102 221.10236 609.4488 C 221.10236 621.9666 228.72189 632.12598 238.11023 632.12598" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(125.33071 596.9488)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="14.896484" y="10" textLength="43.20703">reader</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x=".49414062" y="22" textLength="72.01172">StreamReader</tspan></text><path d="M 385.5118 586.77165 L 521.5748 586.77165 C 530.96315 586.77165 538.58267 596.93102 538.58267 609.4488 C 538.58267 621.9666 530.96315 632.12598 521.5748 632.12598 L 385.5118 632.12598 C 376.12346 632.12598 368.50393 621.9666 368.50393 609.4488 C 368.50393 596.93102 376.12346 586.77165 385.5118 586.77165" fill="#fc6"/><path d="M 385.5118 586.77165 L 521.5748 586.77165 C 530.96315 586.77165 538.58267 596.93102 538.58267 609.4488 C 538.58267 621.9666 530.96315 632.12598 521.5748 632.12598 L 385.5118 632.12598 C 376.12346 632.12598 368.50393 621.9666 368.50393 609.4488 C 368.50393 596.93102 376.12346 586.77165 385.5118 586.77165 M 521.5748 586.77165 C 512.18645 586.77165 504.56693 596.93102 504.56693 609.4488 C 504.56693 621.9666 512.18645 632.12598 521.5748 632.12598" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(408.79527 596.9488)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="14.896484" y="10" textLength="43.20703">writer</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x=".49414062" y="22" textLength="72.01172">StreamWriter</tspan></text><path d="M 481.88976 419.52756 L 481.88976 374.17323 C 481.88976 371.04378 469.19055 368.50393 453.5433 368.50393 C 437.89606 368.50393 425.19685 371.04378 425.19685 374.17323 L 425.19685 419.52756 C 425.19685 422.657 437.89606 425.19685 453.5433 425.19685 C 469.19055 425.19685 481.88976 422.657 481.88976 419.52756" fill="#fecc66"/><path d="M 481.88976 419.52756 L 481.88976 374.17323 C 481.88976 371.04378 469.19055 368.50393 453.5433 368.50393 C 437.89606 368.50393 425.19685 371.04378 425.19685 374.17323 L 425.19685 419.52756 C 425.19685 422.657 437.89606 425.19685 453.5433 425.19685 C 469.19055 425.19685 481.88976 422.657 481.88976 419.52756 M 481.88976 374.17323 C 481.88976 377.30267 469.19055 379.84252 453.5433 379.84252 C 437.89606 379.84252 425.19685 377.30267 425.19685 374.17323" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(429.19685 387.18504)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="500" x="6.343527" y="10" textLength="36.00586">pings</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="12.3445034" y="22" textLength="24.003906">dict</tspan></text><path d="M 85.039413 283.46457 L 255.11806 283.46457 C 270.7734 283.46457 283.46457 296.15573 283.46457 311.81107 L 283.46457 481.88972 C 283.46457 497.54506 270.7734 510.23622 255.11806 510.23622 L 85.039413 510.23622 C 69.384074 510.23622 56.692913 497.54506 56.692913 481.88972 L 56.692913 311.81107 C 56.692913 296.15573 69.384074 283.46457 85.039413 283.46457 Z" fill="#dadada"/><path d="M 85.039413 283.46457 L 255.11806 283.46457 C 270.7734 283.46457 283.46457 296.15573 283.46457 311.81107 L 283.46457 481.88972 C 283.46457 497.54506 270.7734 510.23622 255.11806 510.23622 L 85.039413 510.23622 C 69.384074 510.23622 56.692913 497.54506 56.692913 481.88972 L 56.692913 311.81107 C 56.692913 296.15573 69.384074 283.46457 85.039413 283.46457 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(61.692913 288.46457)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="43.57528" y="10" textLength="129.62109">transfer_data_task</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="96.383873" y="22" textLength="24.003906">Task</tspan></text><path d="M 297.6378 765.35433 L 297.6378 609.4488 L 255.11811 609.4488 L 269.01811 609.4488 L 266.51811 609.4488" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 368.50393 609.4488 L 354.60393 609.4488 L 325.98425 609.4488 L 325.98425 753.95433" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 207.03401 712.3154 C 161.22047 708.6614 179.48976 677.90097 252.5726 683.1496 C 259.35307 672.91835 344.33858 674.579 343.783 683.1496 C 397.0715 672.1877 465.17102 694.04553 419.49354 705.00744 C 474.30425 710.32206 418.80189 738.9565 373.8189 734.17322 C 370.2189 742.14584 289.80283 744.9358 282.74457 734.17322 C 237.20882 745.66715 142.25953 727.9946 207.03401 712.3154 Z" fill="url(#Obj_Gradient)"/><path d="M 207.03401 712.3154 C 161.22047 708.6614 179.48976 677.90097 252.5726 683.1496 C 259.35307 672.91835 344.33858 674.579 343.783 683.1496 C 397.0715 672.1877 465.17102 694.04553 419.49354 705.00744 C 474.30425 710.32206 418.80189 738.9565 373.8189 734.17322 C 370.2189 742.14584 289.80283 744.9358 282.74457 734.17322 C 237.20882 745.66715 142.25953 727.9946 207.03401 712.3154 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(217.59842 701.1614)" fill="black"><tspan font-family="Verdana" font-size="12" font-weight="500" x="69.81416" y="12" textLength="48.796875">network</tspan></text><rect x="85.03937" y="453.5433" width="170.07874" height="28.346457" fill="#ff6"/><rect x="85.03937" y="453.5433" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 460.71653)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="44.03351" y="10" textLength="72.01172">read_frame</tspan></text><rect x="85.03937" y="396.8504" width="170.07874" height="28.346457" fill="#ff6"/><rect x="85.03937" y="396.8504" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 404.02362)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="26.03058" y="10" textLength="108.01758">read_data_frame</tspan></text><rect x="85.03937" y="340.15748" width="170.07874" height="28.346457" fill="#ff6"/><rect x="85.03937" y="340.15748" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 347.3307)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="36.832338" y="10" textLength="86.41406">read_message</tspan></text><text transform="translate(178.07874 490.563)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="30.004883">bytes</tspan></text><text transform="translate(178.07874 433.87008)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="36.00586">frames</tspan></text><text transform="translate(178.07874 371.67716)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="24.003906">data</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="19" textLength="36.00586">frames</tspan></text><rect x="368.50393" y="510.23622" width="170.07874" height="28.346457" fill="#ff6"/><rect x="368.50393" y="510.23622" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(373.50393 517.40945)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="40.432924" y="10" textLength="79.21289">write_frame</tspan></text><path d="M 85.03937 609.4488 L 71.13937 609.4488 L 56.692913 609.4488 L 56.692913 595.2756 L 56.692913 566.92913 L 113.385826 566.92913 L 170.07874 566.92913 L 170.07874 495.78976 L 170.07874 494.03976" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 453.5433 539.33267 L 453.5433 552.48267 L 453.5433 566.92913 L 510.23622 566.92913 L 569.76378 566.92913 L 569.76378 595.2756 L 569.76378 609.4488 L 552.48267 609.4488 L 549.98267 609.4488" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="170.07874" y1="453.5433" x2="170.07874" y2="437.34685" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="170.07874" y1="396.8504" x2="170.07874" y2="380.65393" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 102.047243 204.09449 L 238.11023 204.09449 C 247.49858 204.09449 255.11811 214.25386 255.11811 226.77165 C 255.11811 239.28945 247.49858 249.44882 238.11023 249.44882 L 102.047243 249.44882 C 92.658897 249.44882 85.03937 239.28945 85.03937 226.77165 C 85.03937 214.25386 92.658897 204.09449 102.047243 204.09449" fill="#fc6"/><path d="M 102.047243 204.09449 L 238.11023 204.09449 C 247.49858 204.09449 255.11811 214.25386 255.11811 226.77165 C 255.11811 239.28945 247.49858 249.44882 238.11023 249.44882 L 102.047243 249.44882 C 92.658897 249.44882 85.03937 239.28945 85.03937 226.77165 C 85.03937 214.25386 92.658897 204.09449 102.047243 204.09449 M 238.11023 204.09449 C 228.72189 204.09449 221.10236 214.25386 221.10236 226.77165 C 221.10236 239.28945 228.72189 249.44882 238.11023 249.44882" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(132.33071 214.27165)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x=".1953125" y="10" textLength="57.609375">messages</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="13.997559" y="22" textLength="30.004883">deque</tspan></text><path d="M 255.11811 354.3307 L 269.01811 354.3307 L 297.6378 354.3307 L 297.6378 328.8189 L 297.6378 226.77165 L 269.01811 226.77165 L 266.51811 226.77165" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><rect x="85.03937" y="141.73228" width="170.07874" height="28.346457" fill="#cf6"/><rect x="85.03937" y="141.73228" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(90.03937 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="65.637026" y="10" textLength="28.804688">recv</tspan></text><path d="M 85.03937 226.77165 L 71.13937 226.77165 L 42.519685 226.77165 L 42.519685 209.76378 L 42.519685 155.90551 L 71.13937 155.90551 L 73.63937 155.90551" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="170.07874" y1="141.73228" x2="170.07874" y2="68.092913" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="453.5433" y1="56.692913" x2="453.5433" y2="130.33228" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="467.71653" y1="56.692913" x2="467.71653" y2="187.8752" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="481.88976" y1="56.692913" x2="481.88976" y2="244.56811" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="496.063" y1="56.692913" x2="496.063" y2="300.32302" marker-end="url(#StickArrow_Marker_3)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><rect x="368.50393" y="141.73228" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="141.73228" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(373.50393 148.90551)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="65.637026" y="10" textLength="28.804688">send</tspan></text><rect x="368.50393" y="198.4252" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="198.4252" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(373.50393 205.59842)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="500" x="65.637026" y="10" textLength="28.804688">ping</tspan></text><rect x="368.50393" y="255.11811" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="255.11811" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(373.50393 262.29134)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="500" x="65.637026" y="10" textLength="28.804688">pong</tspan></text><rect x="368.50393" y="311.81102" width="170.07874" height="28.346457" fill="#cf6"/><rect x="368.50393" y="311.81102" width="170.07874" height="28.346457" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(373.50393 318.98425)" fill="black"><tspan font-family="Courier New" font-size="12" font-weight="bold" x="62.03644" y="10" textLength="36.00586">close</tspan></text><path d="M 538.58267 155.90551 L 552.48267 155.90551 L 566.92913 155.90551 L 566.92913 481.88976 L 453.5433 481.88976 L 453.5433 496.33622 L 453.5433 498.08622" marker-end="url(#StickArrow_Marker)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><line x1="538.58267" y1="212.59842" x2="566.92913" y2="212.59842" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="538.58267" y1="269.29134" x2="566.92913" y2="269.29134" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><line x1="538.58267" y1="325.98425" x2="566.92913" y2="325.98425" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><path d="M 255.86811 411.02362 L 262.61811 411.02362 L 340.15748 411.02362 L 340.15748 481.88976 L 453.5433 481.88976" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/><text transform="translate(291.94527 399.02362)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="42.006836">control</tspan><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="21" textLength="36.00586">frames</tspan></text><line x1="340.15748" y1="411.02362" x2="414.64685" y2="411.02362" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><path d="M 368.50393 212.59842 L 361.75393 212.59842 L 340.15748 212.59842 L 340.15748 340.15748 L 340.15748 382.67716" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/><text transform="translate(461.5433 547.2559)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="30.004883">bytes</tspan></text><text transform="translate(461.5433 490.563)" fill="black"><tspan font-family="Courier New" font-size="10" font-weight="500" x="0" y="8" textLength="36.00586">frames</tspan></text><line x1="340.15748" y1="382.67716" x2="414.64685" y2="382.67716" marker-end="url(#StickArrow_Marker_2)" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width=".75"/></g></g></svg> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/requirements.txt b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/requirements.txt new file mode 100644 index 00000000000..0eaf94fbe8f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/requirements.txt @@ -0,0 +1,4 @@ +sphinx +sphinx-autodoc-typehints +sphinxcontrib-spelling +sphinxcontrib-trio diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/security.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/security.rst new file mode 100644 index 00000000000..e9acf0629c8 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/security.rst @@ -0,0 +1,39 @@ +Security +======== + +Encryption +---------- + +For production use, a server should require encrypted connections. + +See this example of :ref:`encrypting connections with TLS +<secure-server-example>`. + +Memory use +---------- + +.. warning:: + + An attacker who can open an arbitrary number of connections will be able + to perform a denial of service by memory exhaustion. If you're concerned + by denial of service attacks, you must reject suspicious connections + before they reach ``websockets``, typically in a reverse proxy. + +With the default settings, opening a connection uses 325 KiB of memory. + +Sending some highly compressed messages could use up to 128 MiB of memory +with an amplification factor of 1000 between network traffic and memory use. + +Configuring a server to :ref:`optimize memory usage <memory-usage>` will +improve security in addition to improving performance. + +Other limits +------------ + +``websockets`` implements additional limits on the amount of data it accepts +in order to minimize exposure to security vulnerabilities. + +In the opening handshake, ``websockets`` limits the number of HTTP headers to +256 and the size of an individual header to 4096 bytes. These limits are 10 to +20 times larger than what's expected in standard use cases. They're hard-coded. +If you need to change them, monkey-patch the constants in ``websockets.http``. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/spelling_wordlist.txt b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/spelling_wordlist.txt new file mode 100644 index 00000000000..1eacc491df7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/spelling_wordlist.txt @@ -0,0 +1,39 @@ +attr +augustin +Auth +awaitable +aymeric +backpressure +Backpressure +Bitcoin +bufferbloat +Bufferbloat +bugfix +bytestring +bytestrings +changelog +cryptocurrency +daemonize +fractalideas +iterable +keepalive +KiB +lifecycle +Lifecycle +MiB +nginx +permessage +pong +pongs +Pythonic +serializers +subclassing +subprotocol +subprotocols +TLS +Unparse +uple +username +websocket +WebSocket +websockets diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/tidelift.rst b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/tidelift.rst new file mode 100644 index 00000000000..43b457aafa8 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/docs/tidelift.rst @@ -0,0 +1,112 @@ +websockets for enterprise +========================= + +Available as part of the Tidelift Subscription +---------------------------------------------- + +.. image:: _static/tidelift.png + :height: 150px + :width: 150px + :align: left + +Tidelift is working with the maintainers of websockets and thousands of other +open source projects to deliver commercial support and maintenance for the +open source dependencies you use to build your applications. Save time, reduce +risk, and improve code health, while paying the maintainers of the exact +dependencies you use. + +.. raw:: html + + <style type="text/css"> + .tidelift-links { + display: flex; + justify-content: center; + } + @media only screen and (max-width: 600px) { + .tidelift-links { + flex-direction: column; + } + } + .tidelift-links a { + border: thin solid #f6914d; + border-radius: 0.25em; + font-family: Verdana, sans-serif; + font-size: 15px; + margin: 0.5em 2em; + padding: 0.5em 2em; + text-align: center; + text-decoration: none; + text-transform: uppercase; + } + .tidelift-links a.tidelift-links__learn-more { + background-color: white; + color: #f6914d; + } + .tidelift-links a.tidelift-links__request-a-demo { + background-color: #f6914d; + color: white; + } + </style> + + <div class="tidelift-links"> + <a class="tidelift-links__learn-more" href="https://tidelift.com/subscription/pkg/pypi-websockets?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=enterprise">Learn more</a> + <a class="tidelift-links__request-a-demo" href="https://tidelift.com/subscription/request-a-demo?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=enterprise">Request a demo</a> + </div> + +Enterprise-ready open source software—managed for you +----------------------------------------------------- + +The Tidelift Subscription is a managed open source subscription for +application dependencies covering millions of open source projects across +JavaScript, Python, Java, PHP, Ruby, .NET, and more. + +Your subscription includes: + +* **Security updates** + + * Tidelift’s security response team coordinates patches for new breaking + security vulnerabilities and alerts immediately through a private channel, + so your software supply chain is always secure. + +* **Licensing verification and indemnification** + + * Tidelift verifies license information to enable easy policy enforcement + and adds intellectual property indemnification to cover creators and users + in case something goes wrong. You always have a 100% up-to-date bill of + materials for your dependencies to share with your legal team, customers, + or partners. + +* **Maintenance and code improvement** + + * Tidelift ensures the software you rely on keeps working as long as you + need it to work. Your managed dependencies are actively maintained and we + recruit additional maintainers where required. + +* **Package selection and version guidance** + + * We help you choose the best open source packages from the start—and then + guide you through updates to stay on the best releases as new issues + arise. + +* **Roadmap input** + + * Take a seat at the table with the creators behind the software you use. + Tidelift’s participating maintainers earn more income as their software is + used by more subscribers, so they’re interested in knowing what you need. + +* **Tooling and cloud integration** + + * Tidelift works with GitHub, GitLab, BitBucket, and more. We support every + cloud platform (and other deployment targets, too). + +The end result? All of the capabilities you expect from commercial-grade +software, for the full breadth of open source you use. That means less time +grappling with esoteric open source trivia, and more time building your own +applications—and your business. + +.. raw:: html + + <div class="tidelift-links"> + <a class="tidelift-links__learn-more" href="https://tidelift.com/subscription/pkg/pypi-websockets?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=enterprise">Learn more</a> + <a class="tidelift-links__request-a-demo" href="https://tidelift.com/subscription/request-a-demo?utm_source=pypi-websockets&utm_medium=referral&utm_campaign=enterprise">Request a demo</a> + </div> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/basic_auth_client.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/basic_auth_client.py new file mode 100755 index 00000000000..cc94dbe4b49 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/basic_auth_client.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +# WS client example with HTTP Basic Authentication + +import asyncio +import websockets + +async def hello(): + uri = "ws://mary:p@ssw0rd@localhost:8765" + async with websockets.connect(uri) as websocket: + greeting = await websocket.recv() + print(greeting) + +asyncio.get_event_loop().run_until_complete(hello()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/basic_auth_server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/basic_auth_server.py new file mode 100755 index 00000000000..6740d57989c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/basic_auth_server.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +# Server example with HTTP Basic Authentication over TLS + +import asyncio +import websockets + +async def hello(websocket, path): + greeting = f"Hello {websocket.username}!" + await websocket.send(greeting) + +start_server = websockets.serve( + hello, "localhost", 8765, + create_protocol=websockets.basic_auth_protocol_factory( + realm="example", credentials=("mary", "p@ssw0rd") + ), +) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/client.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/client.py new file mode 100755 index 00000000000..4f969c478ad --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/client.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +# WS client example + +import asyncio +import websockets + +async def hello(): + uri = "ws://localhost:8765" + async with websockets.connect(uri) as websocket: + name = input("What's your name? ") + + await websocket.send(name) + print(f"> {name}") + + greeting = await websocket.recv() + print(f"< {greeting}") + +asyncio.get_event_loop().run_until_complete(hello()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/counter.html b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/counter.html new file mode 100644 index 00000000000..6310c4a16d9 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/counter.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html> + <head> + <title>WebSocket demo</title> + <style type="text/css"> + body { + font-family: "Courier New", sans-serif; + text-align: center; + } + .buttons { + font-size: 4em; + display: flex; + justify-content: center; + } + .button, .value { + line-height: 1; + padding: 2rem; + margin: 2rem; + border: medium solid; + min-height: 1em; + min-width: 1em; + } + .button { + cursor: pointer; + user-select: none; + } + .minus { + color: red; + } + .plus { + color: green; + } + .value { + min-width: 2em; + } + .state { + font-size: 2em; + } + </style> + </head> + <body> + <div class="buttons"> + <div class="minus button">-</div> + <div class="value">?</div> + <div class="plus button">+</div> + </div> + <div class="state"> + <span class="users">?</span> online + </div> + <script> + var minus = document.querySelector('.minus'), + plus = document.querySelector('.plus'), + value = document.querySelector('.value'), + users = document.querySelector('.users'), + websocket = new WebSocket("ws://127.0.0.1:6789/"); + minus.onclick = function (event) { + websocket.send(JSON.stringify({action: 'minus'})); + } + plus.onclick = function (event) { + websocket.send(JSON.stringify({action: 'plus'})); + } + websocket.onmessage = function (event) { + data = JSON.parse(event.data); + switch (data.type) { + case 'state': + value.textContent = data.value; + break; + case 'users': + users.textContent = ( + data.count.toString() + " user" + + (data.count == 1 ? "" : "s")); + break; + default: + console.error( + "unsupported event", data); + } + }; + </script> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/counter.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/counter.py new file mode 100755 index 00000000000..dbbbe593580 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/counter.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# WS server example that synchronizes state across clients + +import asyncio +import json +import logging +import websockets + +logging.basicConfig() + +STATE = {"value": 0} + +USERS = set() + + +def state_event(): + return json.dumps({"type": "state", **STATE}) + + +def users_event(): + return json.dumps({"type": "users", "count": len(USERS)}) + + +async def notify_state(): + if USERS: # asyncio.wait doesn't accept an empty list + message = state_event() + await asyncio.wait([user.send(message) for user in USERS]) + + +async def notify_users(): + if USERS: # asyncio.wait doesn't accept an empty list + message = users_event() + await asyncio.wait([user.send(message) for user in USERS]) + + +async def register(websocket): + USERS.add(websocket) + await notify_users() + + +async def unregister(websocket): + USERS.remove(websocket) + await notify_users() + + +async def counter(websocket, path): + # register(websocket) sends user_event() to websocket + await register(websocket) + try: + await websocket.send(state_event()) + async for message in websocket: + data = json.loads(message) + if data["action"] == "minus": + STATE["value"] -= 1 + await notify_state() + elif data["action"] == "plus": + STATE["value"] += 1 + await notify_state() + else: + logging.error("unsupported event: {}", data) + finally: + await unregister(websocket) + + +start_server = websockets.serve(counter, "localhost", 6789) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/echo.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/echo.py new file mode 100755 index 00000000000..b7ca38d321f --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/echo.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +import asyncio +import websockets + +async def echo(websocket, path): + async for message in websocket: + await websocket.send(message) + +start_server = websockets.serve(echo, "localhost", 8765) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/health_check_server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/health_check_server.py new file mode 100755 index 00000000000..417063fce7e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/health_check_server.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +# WS echo server with HTTP endpoint at /health/ + +import asyncio +import http +import websockets + +async def health_check(path, request_headers): + if path == "/health/": + return http.HTTPStatus.OK, [], b"OK\n" + +async def echo(websocket, path): + async for message in websocket: + await websocket.send(message) + +start_server = websockets.serve( + echo, "localhost", 8765, process_request=health_check +) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/hello.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/hello.py new file mode 100755 index 00000000000..6c9c839d827 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/hello.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +import asyncio +import websockets + +async def hello(): + uri = "ws://localhost:8765" + async with websockets.connect(uri) as websocket: + await websocket.send("Hello world!") + await websocket.recv() + +asyncio.get_event_loop().run_until_complete(hello()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/localhost.pem b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/localhost.pem new file mode 100644 index 00000000000..f9a30ba8f63 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/localhost.pem @@ -0,0 +1,48 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDG8iDak4UBpurI +TWjSfqJ0YVG/S56nhswehupCaIzu0xQ8wqPSs36h5t1jMexJPZfvwyvFjcV+hYpj +LMM0wMJPx9oBQEe0bsmlC66e8aF0UpSQw1aVfYoxA9BejgEyrFNE7cRbQNYFEb/5 +3HfqZKdEQA2fgQSlZ0RTRmLrD+l72iO5o2xl5bttXpqYZB2XOkyO79j/xWdu9zFE +sgZJ5ysWbqoRAGgnxjdYYr9DARd8bIE/hN3SW7mDt5v4LqCIhGn1VmrwtT3d5AuG +QPz4YEbm0t6GOlmFjIMYH5Y7pALRVfoJKRj6DGNIR1JicL+wqLV66kcVnj8WKbla +20i7fR7NAgMBAAECggEAG5yvgqbG5xvLqlFUIyMAWTbIqcxNEONcoUAIc38fUGZr +gKNjKXNQOBha0dG0AdZSqCxmftzWdGEEfA9SaJf4YCpUz6ekTB60Tfv5GIZg6kwr +4ou6ELWD4Jmu6fC7qdTRGdgGUMQG8F0uT/eRjS67KHXbbi/x/SMAEK7MO+PRfCbj ++JGzS9Ym9mUweINPotgjHdDGwwd039VWYS+9A+QuNK27p3zq4hrWRb4wshSC8fKy +oLoe4OQt81aowpX9k6mAU6N8vOmP8/EcQHYC+yFIIDZB2EmDP07R1LUEH3KJnzo7 +plCK1/kYPhX0a05cEdTpXdKa74AlvSRkS11sGqfUAQKBgQDj1SRv0AUGsHSA0LWx +a0NT1ZLEXCG0uqgdgh0sTqIeirQsPROw3ky4lH5MbjkfReArFkhHu3M6KoywEPxE +wanSRh/t1qcNjNNZUvFoUzAKVpb33RLkJppOTVEWPt+wtyDlfz1ZAXzMV66tACrx +H2a3v0ZWUz6J+x/dESH5TTNL4QKBgQDfirmknp408pwBE+bulngKy0QvU09En8H0 +uvqr8q4jCXqJ1tXon4wsHg2yF4Fa37SCpSmvONIDwJvVWkkYLyBHKOns/fWCkW3n +hIcYx0q2jgcoOLU0uoaM9ArRXhIxoWqV/KGkQzN+3xXC1/MxZ5OhyxBxfPCPIYIN +YN3M1t/QbQKBgDImhsC+D30rdlmsl3IYZFed2ZKznQ/FTqBANd+8517FtWdPgnga +VtUCitKUKKrDnNafLwXrMzAIkbNn6b/QyWrp2Lln2JnY9+TfpxgJx7de3BhvZ2sl +PC4kQsccy+yAQxOBcKWY+Dmay251bP5qpRepWPhDlq6UwqzMyqev4KzBAoGAWDMi +IEO9ZGK9DufNXCHeZ1PgKVQTmJ34JxmHQkTUVFqvEKfFaq1Y3ydUfAouLa7KSCnm +ko42vuhGFB41bOdbMvh/o9RoBAZheNGfhDVN002ioUoOpSlbYU4A3q7hOtfXeCpf +lLI3JT3cFi6ic8HMTDAU4tJLEA5GhATOPr4hPNkCgYB8jTYGcLvoeFaLEveg0kS2 +cz6ZXGLJx5m1AOQy5g9FwGaW+10lr8TF2k3AldwoiwX0R6sHAf/945aGU83ms5v9 +PB9/x66AYtSRUos9MwB4y1ur4g6FiXZUBgTJUqzz2nehPCyGjYhh49WucjszqcjX +chS1bKZOY+1knWq8xj5Qyg== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDTTCCAjWgAwIBAgIJAOjte6l+03jvMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV +BAYTAkZSMQ4wDAYDVQQHDAVQYXJpczEZMBcGA1UECgwQQXltZXJpYyBBdWd1c3Rp +bjESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MDUwNTE2NTkyOVoYDzIwNjAwNTA0 +MTY1OTI5WjBMMQswCQYDVQQGEwJGUjEOMAwGA1UEBwwFUGFyaXMxGTAXBgNVBAoM +EEF5bWVyaWMgQXVndXN0aW4xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMbyINqThQGm6shNaNJ+onRhUb9LnqeGzB6G +6kJojO7TFDzCo9KzfqHm3WMx7Ek9l+/DK8WNxX6FimMswzTAwk/H2gFAR7RuyaUL +rp7xoXRSlJDDVpV9ijED0F6OATKsU0TtxFtA1gURv/ncd+pkp0RADZ+BBKVnRFNG +YusP6XvaI7mjbGXlu21emphkHZc6TI7v2P/FZ273MUSyBknnKxZuqhEAaCfGN1hi +v0MBF3xsgT+E3dJbuYO3m/guoIiEafVWavC1Pd3kC4ZA/PhgRubS3oY6WYWMgxgf +ljukAtFV+gkpGPoMY0hHUmJwv7CotXrqRxWePxYpuVrbSLt9Hs0CAwEAAaMwMC4w +LAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0G +CSqGSIb3DQEBCwUAA4IBAQC9TsTxTEvqHPUS6sfvF77eG0D6HLOONVN91J+L7LiX +v3bFeS1xbUS6/wIxZi5EnAt/te5vaHk/5Q1UvznQP4j2gNoM6lH/DRkSARvRitVc +H0qN4Xp2Yk1R9VEx4ZgArcyMpI+GhE4vJRx1LE/hsuAzw7BAdsTt9zicscNg2fxO +3ao/eBcdaC6n9aFYdE6CADMpB1lCX2oWNVdj6IavQLu7VMc+WJ3RKncwC9th+5OP +ISPvkVZWf25rR2STmvvb0qEm3CZjk4Xd7N+gxbKKUvzEgPjrLSWzKKJAWHjCLugI +/kQqhpjWVlTbtKzWz5bViqCjSbrIPpU2MgG9AUV9y3iV +-----END CERTIFICATE----- diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/secure_client.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/secure_client.py new file mode 100755 index 00000000000..54971b9847e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/secure_client.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +# WSS (WS over TLS) client example, with a self-signed certificate + +import asyncio +import pathlib +import ssl +import websockets + +ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) +localhost_pem = pathlib.Path(__file__).with_name("localhost.pem") +ssl_context.load_verify_locations(localhost_pem) + +async def hello(): + uri = "wss://localhost:8765" + async with websockets.connect( + uri, ssl=ssl_context + ) as websocket: + name = input("What's your name? ") + + await websocket.send(name) + print(f"> {name}") + + greeting = await websocket.recv() + print(f"< {greeting}") + +asyncio.get_event_loop().run_until_complete(hello()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/secure_server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/secure_server.py new file mode 100755 index 00000000000..2a00bdb5042 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/secure_server.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# WSS (WS over TLS) server example, with a self-signed certificate + +import asyncio +import pathlib +import ssl +import websockets + +async def hello(websocket, path): + name = await websocket.recv() + print(f"< {name}") + + greeting = f"Hello {name}!" + + await websocket.send(greeting) + print(f"> {greeting}") + +ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +localhost_pem = pathlib.Path(__file__).with_name("localhost.pem") +ssl_context.load_cert_chain(localhost_pem) + +start_server = websockets.serve( + hello, "localhost", 8765, ssl=ssl_context +) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/server.py new file mode 100755 index 00000000000..c8ab69971b2 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/server.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +# WS server example + +import asyncio +import websockets + +async def hello(websocket, path): + name = await websocket.recv() + print(f"< {name}") + + greeting = f"Hello {name}!" + + await websocket.send(greeting) + print(f"> {greeting}") + +start_server = websockets.serve(hello, "localhost", 8765) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/show_time.html b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/show_time.html new file mode 100644 index 00000000000..721f44264ef --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/show_time.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + <head> + <title>WebSocket demo</title> + </head> + <body> + <script> + var ws = new WebSocket("ws://127.0.0.1:5678/"), + messages = document.createElement('ul'); + ws.onmessage = function (event) { + var messages = document.getElementsByTagName('ul')[0], + message = document.createElement('li'), + content = document.createTextNode(event.data); + message.appendChild(content); + messages.appendChild(message); + }; + document.body.appendChild(messages); + </script> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/show_time.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/show_time.py new file mode 100755 index 00000000000..e5d6ac9aa3b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/show_time.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +# WS server that sends messages at random intervals + +import asyncio +import datetime +import random +import websockets + +async def time(websocket, path): + while True: + now = datetime.datetime.utcnow().isoformat() + "Z" + await websocket.send(now) + await asyncio.sleep(random.random() * 3) + +start_server = websockets.serve(time, "127.0.0.1", 5678) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/shutdown.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/shutdown.py new file mode 100755 index 00000000000..86846abe73e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/shutdown.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import asyncio +import signal +import websockets + +async def echo(websocket, path): + async for message in websocket: + await websocket.send(message) + +async def echo_server(stop): + async with websockets.serve(echo, "localhost", 8765): + await stop + +loop = asyncio.get_event_loop() + +# The stop condition is set when receiving SIGTERM. +stop = loop.create_future() +loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) + +# Run the server until the stop condition is met. +loop.run_until_complete(echo_server(stop)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/unix_client.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/unix_client.py new file mode 100755 index 00000000000..577135b3dbf --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/unix_client.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +# WS client example connecting to a Unix socket + +import asyncio +import os.path +import websockets + +async def hello(): + socket_path = os.path.join(os.path.dirname(__file__), "socket") + async with websockets.unix_connect(socket_path) as websocket: + name = input("What's your name? ") + await websocket.send(name) + print(f"> {name}") + + greeting = await websocket.recv() + print(f"< {greeting}") + +asyncio.get_event_loop().run_until_complete(hello()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/example/unix_server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/unix_server.py new file mode 100755 index 00000000000..a6ec0168a2b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/example/unix_server.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +# WS server example listening on a Unix socket + +import asyncio +import os.path +import websockets + +async def hello(websocket, path): + name = await websocket.recv() + print(f"< {name}") + + greeting = f"Hello {name}!" + + await websocket.send(greeting) + print(f"> {greeting}") + +socket_path = os.path.join(os.path.dirname(__file__), "socket") +start_server = websockets.unix_serve(hello, socket_path) + +asyncio.get_event_loop().run_until_complete(start_server) +asyncio.get_event_loop().run_forever() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/horizontal.svg b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/horizontal.svg new file mode 100644 index 00000000000..ee872dc4786 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/horizontal.svg @@ -0,0 +1,31 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="256" viewBox="0 0 1024 256"> + <linearGradient id="w" x1="0" y1="0" x2="0.1667" y2="0.6667"> + <stop offset="0%" stop-color="#ffe873" /> + <stop offset="100%" stop-color="#ffd43b" /> + </linearGradient> + <linearGradient id="s" x1="0" y1="0" x2="0.1667" y2="0.6667"> + <stop offset="0%" stop-color="#5a9fd4" /> + <stop offset="100%" stop-color="#306998" /> + </linearGradient> +<g> + <path fill="url(#w)" d="m 151.60708,154.81618 c -0.43704,0.0747 -0.88656,0.12978 -1.35572,0.14933 -2.45813,0.0764 -4.25357,-0.58665 -5.82335,-2.15107 l -8.89246,-8.85942 -11.23464,-11.19805 -36.040757,-35.919452 c -3.43568,-3.42217 -7.332485,-5.347474 -11.589626,-5.723468 -2.229803,-0.198219 -4.473877,0.03111 -6.640354,0.675545 -3.242133,0.944875 -6.135526,2.664848 -8.593662,5.116366 -3.834369,3.819499 -5.86349,8.414979 -5.875977,13.287799 -0.06065,4.95281 1.951523,9.60074 5.808192,13.44424 l 55.622894,55.43648 c 1.82219,1.84175 2.65971,3.79549 2.63384,6.14568 l 0.004,0.208 c 0.0527,2.43196 -0.75991,4.34571 -2.6267,6.20612 -1.78028,1.77598 -3.8094,2.65241 -6.30945,2.75552 -2.45814,0.0764 -4.25446,-0.58844 -5.82514,-2.15286 L 48.702551,136.2618 c -5.214172,-5.19459 -11.702899,-6.98745 -18.22998,-5.04881 -3.245701,0.9431 -6.135527,2.66307 -8.595446,5.11459 -3.83437,3.82127 -5.865275,8.41676 -5.875978,13.28957 -0.05619,4.95281 1.951524,9.60252 5.806409,13.4478 l 58.10689,57.90577 c 8.319842,8.29143 19.340421,11.9376 32.743314,10.83806 12.57967,-1.02043 23.02317,-5.5848 31.03441,-13.57313 7.51265,-7.4861 11.96423,-16.35175 13.28695,-26.42537 10.47206,-1.68264 19.29494,-6.04524 26.27512,-13.00158 4.01364,-3.99994 7.14963,-8.3972 9.40531,-13.16157 -14.15569,-0.39911 -28.23645,-4.00972 -41.05247,-10.83095 z" /> + <path fill="url(#s)" d="m 196.96038,146.11854 c 0.10259,-12.84514 -4.43017,-23.98541 -13.50635,-33.1346 L 147.57292,77.225374 c -0.24349,-0.240885 -0.46469,-0.487992 -0.68678,-0.744877 -1.48416,-1.739529 -2.18788,-3.583056 -2.21018,-5.807022 -0.0259,-2.470184 0.84911,-4.508375 2.7605,-6.407902 1.91406,-1.909304 3.8531,-2.737735 6.36564,-2.684403 2.53662,0.024 4.62728,0.943097 6.57257,2.881734 l 60.59178,60.384846 12.11408,-12.06914 c 1.12203,-0.90755 1.95777,-1.76887 2.87823,-2.93418 5.91879,-7.51544 5.26947,-18.272609 -1.51003,-25.02895 L 187.20456,37.727314 c -9.19393,-9.157192 -20.36703,-13.776677 -33.16789,-13.7269 -12.94266,-0.05067 -24.14163,4.548375 -33.28739,13.662901 -9.02892,8.996307 -13.64015,19.93925 -13.7008,32.487501 l -0.004,0.14222 c -0.002,0.167998 -0.005,0.336884 -0.005,0.506659 -0.091,12.232701 4.10729,22.95787 12.48154,31.881285 0.40226,0.43022 0.80274,0.85777 1.22283,1.27821 l 35.75088,35.62612 c 1.88909,1.88174 2.71769,3.79638 2.69361,6.20968 l 0.003,0.20977 c 0.0527,2.43197 -0.76081,4.34571 -2.6276,6.20791 -1.44759,1.43909 -3.06286,2.27818 -4.9564,2.60262 12.81601,6.82123 26.89677,10.43184 41.05246,10.83362 2.80598,-5.92525 4.2509,-12.41848 4.29906,-19.43526 z" /> + <path fill="#ffffff" d="m 215.68093,93.181574 c 2.84701,-2.838179 7.46359,-2.836401 10.30883,0 2.84433,2.834623 2.84612,7.435446 -0.002,10.270956 -2.84345,2.83818 -7.46271,2.83818 -10.30704,0 -2.84524,-2.83551 -2.84791,-7.435444 0,-10.270956 z" /> + </g> + <g> + <g fill="#ffd43b"> + <path d="m 271.62046,177.33313 c 0,4.1637 1.46619,7.71227 4.39858,10.64361 2.9324,2.93556 6.48202,4.40069 10.64783,4.40069 4.16475,0 7.71438,-1.46513 10.64572,-4.40069 2.93344,-2.93134 4.40069,-6.47991 4.40069,-10.64361 v -35.00332 c 0,-2.12345 0.7647,-3.95198 2.29514,-5.48032 1.53045,-1.53256 3.35793,-2.29831 5.48349,-2.29831 h 0.12745 c 2.16664,0 3.972,0.76575 5.41923,2.29831 1.53045,1.52834 2.2962,3.35793 2.2962,5.48032 v 35.00332 c 0,4.1637 1.4662,7.71227 4.40069,10.64361 2.93134,2.93556 6.47886,4.40069 10.64572,4.40069 4.20794,0 7.77758,-1.46513 10.70997,-4.40069 2.93345,-2.93134 4.40069,-6.47991 4.40069,-10.64361 v -35.00332 c 0,-2.12345 0.76365,-3.95198 2.29515,-5.48032 1.44302,-1.53256 3.25049,-2.29831 5.41924,-2.29831 h 0.1264 c 2.12661,0 3.95409,0.76575 5.48349,2.29831 1.48831,1.52834 2.23194,3.35793 2.23194,5.48032 v 35.00332 c 0,8.45696 -2.9977,15.68261 -8.98887,21.67484 -5.99329,5.99224 -13.21999,8.98993 -21.67695,8.98993 -10.11696,0 -17.7239,-3.35583 -22.82609,-10.07272 -5.14222,6.71689 -12.77234,10.07272 -22.88719,10.07272 -8.45801,0 -15.68471,-2.99769 -21.67695,-8.98993 C 258.9998,193.01574 256,185.79113 256,177.33313 v -35.00332 c 0,-2.12345 0.76575,-3.95198 2.29619,-5.48032 1.5294,-1.53256 3.33581,-2.29831 5.41924,-2.29831 h 0.1917 c 2.08238,0 3.88774,0.76575 5.42029,2.29831 1.52834,1.52834 2.29409,3.35793 2.29409,5.48032 v 35.00332 z" /> + <path d="m 443.95216,155.97534 c 0.51085,1.06173 0.7668,2.14346 0.7668,3.25048 0,0.8932 -0.16957,1.78536 -0.50979,2.67854 -0.72363,1.99707 -2.08343,3.4422 -4.0805,4.33434 -5.95114,2.67854 -13.77085,6.20711 -23.46228,10.58463 -12.02871,5.43924 -19.08477,8.64866 -21.16715,9.62823 3.22943,4.07944 8.26737,6.11863 15.11067,6.11863 4.5471,0 8.67077,-1.33769 12.36786,-4.01625 3.61283,-2.63534 6.14286,-6.03541 7.58798,-10.20227 1.23342,-3.48538 3.69815,-5.22754 7.39524,-5.22754 2.6343,0 4.7388,1.10702 6.31138,3.31369 0.97746,1.36193 1.46619,2.78598 1.46619,4.27325 0,0.8932 -0.16958,1.80641 -0.50874,2.74069 -2.50791,7.26988 -6.90861,13.13573 -13.19681,17.59961 -6.37563,4.63031 -13.51702,6.94757 -21.4231,6.94757 -10.11591,0 -18.76563,-3.58965 -25.94809,-10.7742 -7.18351,-7.18353 -10.77527,-15.83219 -10.77527,-25.9502 0,-10.11591 3.59176,-18.76351 10.77527,-25.95019 7.18142,-7.1814 15.83218,-10.77422 25.94809,-10.77422 7.30885,0 13.98257,1.99916 20.01904,5.99223 5.99118,3.91512 10.43296,9.05524 13.32321,15.43298 z m -33.34331,-5.67836 c -5.86583,0 -10.86059,2.06343 -14.98322,6.18604 -4.08049,4.12473 -6.12073,9.11949 -6.12073,14.98322 v 0.44661 l 35.63951,-16.00282 c -3.1441,-3.73817 -7.99035,-5.61305 -14.53556,-5.61305 z" /> + <path d="m 465.12141,108.41246 c 2.08238,0 3.88775,0.74469 5.41924,2.23194 1.53045,1.52834 2.29619,3.35793 2.29619,5.48244 v 24.79998 c 4.80202,-4.24796 11.83701,-6.37564 21.10185,-6.37564 10.11591,0 18.76561,3.59177 25.94914,10.77422 7.18245,7.18563 10.77527,15.83429 10.77527,25.9502 0,10.11695 -3.59282,18.76561 -10.77527,25.95018 C 512.70536,204.41035 504.05566,208 493.93869,208 c -10.11696,0 -18.74349,-3.56964 -25.88382,-10.71207 -7.18457,-7.09504 -10.7974,-15.70262 -10.83954,-25.82063 v -55.33941 c 0,-2.12556 0.76576,-3.95409 2.29621,-5.48243 1.52939,-1.48727 3.3358,-2.23196 5.41924,-2.23196 h 0.19063 z m 28.81622,41.88452 c -5.86477,0 -10.85953,2.06343 -14.9832,6.18604 -4.0784,4.12473 -6.11969,9.11949 -6.11969,14.98322 0,5.8237 2.04129,10.79633 6.11969,14.91896 4.12367,4.12263 9.11737,6.18393 14.9832,6.18393 5.82371,0 10.79635,-2.0613 14.92002,-6.18393 4.12051,-4.12263 6.18288,-9.09526 6.18288,-14.91896 0,-5.86267 -2.06237,-10.85849 -6.18288,-14.98322 -4.12367,-4.12261 -9.09525,-6.18604 -14.92002,-6.18604 z" /> + </g> + <g fill="#306998"> + <path d="m 561.26467,150.17375 c -1.87066,0 -3.44325,0.6362 -4.71773,1.9107 -1.27556,1.31872 -1.91281,2.89025 -1.91281,4.71773 0,2.5511 1.23237,4.46389 3.69919,5.73733 0.84898,0.46872 4.39859,1.53045 10.64678,3.18834 5.05795,1.44619 8.81825,3.33686 11.28296,5.67413 3.52963,3.35898 5.29179,8.14097 5.29179,14.34703 0,6.11862 -2.16769,11.36829 -6.50203,15.74581 -4.37857,4.33644 -9.62823,6.50308 -15.74791,6.50308 h -16.64005 c -2.08448,0 -3.88879,-0.76365 -5.42029,-2.29621 -1.53045,-1.44407 -2.2962,-3.25048 -2.2962,-5.41712 v -0.12953 c 0,-2.12345 0.76575,-3.95198 2.2962,-5.48243 1.53044,-1.53045 3.33581,-2.29619 5.42029,-2.29619 h 17.2773 c 1.8696,0 3.44324,-0.6362 4.71773,-1.9107 1.27556,-1.27554 1.91281,-2.84707 1.91281,-4.71774 0,-2.33937 -1.21131,-4.10366 -3.63285,-5.29073 -0.63723,-0.30018 -4.20898,-1.36192 -10.71208,-3.18834 -5.05899,-1.48725 -8.82139,-3.44535 -11.28611,-5.8669 -3.52856,-3.44217 -5.29075,-8.30949 -5.29075,-14.5998 0,-6.12073 2.16876,-11.34721 6.50414,-15.68261 4.37648,-4.37752 9.62718,-6.56839 15.74687,-6.56839 h 11.73166 c 2.12452,0 3.95304,0.76575 5.48349,2.29831 1.52939,1.52834 2.29515,3.35793 2.29515,5.48032 v 0.12745 c 0,2.16876 -0.76576,3.97622 -2.29515,5.4203 -1.53045,1.52834 -3.35897,2.29619 -5.48349,2.29619 z" /> + <path d="m 630.5677,134.55118 c 10.1159,0 18.76456,3.59177 25.94912,10.77422 7.18246,7.18563 10.77422,15.83429 10.77422,25.9502 0,10.11695 -3.59176,18.76561 -10.77422,25.95018 C 649.33331,204.40929 640.6836,208 630.5677,208 c -10.11592,0 -18.76563,-3.58965 -25.9481,-10.77422 -7.18351,-7.18351 -10.77526,-15.83217 -10.77526,-25.95018 0,-10.11591 3.59175,-18.76352 10.77526,-25.9502 7.18247,-7.18245 15.83218,-10.77422 25.9481,-10.77422 z m 0,15.7458 c -5.86585,0 -10.86059,2.06343 -14.98322,6.18604 -4.08155,4.12473 -6.12178,9.11949 -6.12178,14.98322 0,5.8237 2.04023,10.79633 6.12178,14.91896 4.12263,4.12263 9.11632,6.18393 14.98322,6.18393 5.82264,0 10.79527,-2.0613 14.91896,-6.18393 4.12261,-4.12263 6.18393,-9.09526 6.18393,-14.91896 0,-5.86267 -2.06132,-10.85849 -6.18393,-14.98322 -4.12369,-4.12261 -9.09527,-6.18604 -14.91896,-6.18604 z" /> + <path d="m 724.0345,136.27333 c 3.61388,1.14811 5.4203,3.61282 5.4203,7.39523 v 0.32125 c 0,2.59008 -1.04278,4.65138 -3.12516,6.18394 -1.44512,1.01854 -2.93343,1.52834 -4.46178,1.52834 -0.80894,0 -1.63789,-0.12745 -2.48684,-0.38235 -2.08344,-0.67938 -4.23007,-1.02276 -6.43883,-1.02276 -5.86585,0 -10.86165,2.06343 -14.98322,6.18604 -4.08154,4.12473 -6.12074,9.11949 -6.12074,14.98322 0,5.8237 2.0392,10.79633 6.12074,14.91896 4.12157,4.12263 9.11633,6.18393 14.98322,6.18393 2.20982,0 4.35645,-0.33915 6.43883,-1.02065 0.80683,-0.25489 1.61471,-0.38234 2.42259,-0.38234 1.57046,0 3.08197,0.5119 4.52709,1.53254 2.08238,1.52835 3.12514,3.61283 3.12514,6.24819 0,3.74027 -1.80746,6.205 -5.42028,7.39524 -3.56964,1.10491 -7.26673,1.65579 -11.09232,1.65579 -10.11591,0 -18.76562,-3.58965 -25.95019,-10.77423 -7.1814,-7.18351 -10.77422,-15.83217 -10.77422,-25.95018 0,-10.11592 3.59176,-18.76352 10.77422,-25.9502 7.18351,-7.1814 15.83322,-10.77422 25.95019,-10.77422 3.82348,0.002 7.52162,0.57827 11.09126,1.72426 z" /> + <path d="m 748.19829,108.41246 c 2.08132,0 3.88773,0.74469 5.42029,2.23194 1.5294,1.52834 2.29514,3.35793 2.29514,5.48244 v 44.18284 h 2.42259 c 5.44031,0 10.17805,-1.80642 14.21852,-5.4203 3.95198,-3.61283 6.20394,-8.07461 6.75693,-13.38852 0.25491,-1.99705 1.10597,-3.63494 2.5511,-4.90837 1.44408,-1.35982 3.16517,-2.04131 5.16328,-2.04131 h 0.19066 c 2.25405,0 4.14578,0.85212 5.67517,2.55109 1.36087,1.48727 2.04026,3.20942 2.04026,5.16329 0,0.25491 -0.0222,0.53298 -0.0632,0.82895 -1.02064,10.66889 -5.10115,18.65923 -12.24147,23.97103 3.73922,2.29831 7.18246,6.18604 10.32973,11.66849 3.27155,5.65306 4.90944,11.75483 4.90944,18.29688 v 3.25471 c 0,2.16664 -0.7668,3.972 -2.29515,5.41713 -1.53255,1.53256 -3.33791,2.29619 -5.4203,2.29619 h -0.1917 c -2.08342,0 -3.88879,-0.76363 -5.41922,-2.29619 -1.53045,-1.44408 -2.29514,-3.25049 -2.29514,-5.41713 v -3.25471 c -0.0442,-5.77629 -2.10555,-10.73102 -6.185,-14.85575 -4.12367,-4.07944 -9.09736,-6.11863 -14.91896,-6.11863 h -5.22754 v 24.22804 c 0,2.16664 -0.76574,3.97199 -2.29514,5.41712 -1.5315,1.53256 -3.33897,2.29621 -5.42028,2.29621 h -0.19381 c -2.08237,0 -3.88668,-0.76365 -5.41819,-2.29621 -1.52939,-1.44407 -2.29515,-3.25048 -2.29515,-5.41712 v -84.15879 c 0,-2.12556 0.76576,-3.95408 2.29515,-5.48243 1.53045,-1.48727 3.33582,-2.23195 5.41819,-2.23195 h 0.19381 z" /> + <path d="m 876.85801,155.97534 c 0.5098,1.06173 0.76469,2.14346 0.76469,3.25048 0,0.8932 -0.17063,1.78536 -0.50874,2.67854 -0.72362,1.99707 -2.08342,3.4422 -4.08049,4.33434 -5.95115,2.67854 -13.77191,6.20711 -23.46229,10.58463 -12.02869,5.43924 -19.08476,8.64866 -21.16715,9.62823 3.22838,4.07944 8.26632,6.11863 15.11066,6.11863 4.54606,0 8.66973,-1.33769 12.36893,-4.01625 3.61176,-2.63534 6.14075,-6.03541 7.58587,-10.20227 1.23238,-3.48538 3.6992,-5.22754 7.39524,-5.22754 2.63536,0 4.73985,1.10702 6.31348,3.31369 0.97536,1.36193 1.46515,2.78598 1.46515,4.27325 0,0.8932 -0.16958,1.80641 -0.5098,2.74069 -2.50791,7.26988 -6.9065,13.13573 -13.19681,17.59961 -6.37563,4.63031 -13.51598,6.94757 -21.42206,6.94757 -10.1159,0 -18.76561,-3.58965 -25.94808,-10.7742 -7.18351,-7.18353 -10.77526,-15.83219 -10.77526,-25.9502 0,-10.11591 3.59175,-18.76351 10.77526,-25.95019 7.18141,-7.1814 15.83218,-10.77422 25.94808,-10.77422 7.30887,0 13.98364,1.99916 20.01906,5.99223 5.99223,3.91512 10.43294,9.05524 13.32426,15.43298 z m -33.34436,-5.67836 c -5.86479,0 -10.86059,2.06343 -14.98322,6.18604 -4.08049,4.12473 -6.12074,9.11949 -6.12074,14.98322 v 0.44661 l 35.63952,-16.00282 c -3.14516,-3.73817 -7.99034,-5.61305 -14.53556,-5.61305 z" /> + <path d="m 898.02411,108.41246 c 2.08238,0 3.88879,0.74469 5.42028,2.23194 1.52939,1.52834 2.29515,3.35793 2.29515,5.48244 v 18.42434 h 9.56398 c 2.08237,0 3.88772,0.76575 5.42028,2.29831 1.5294,1.52834 2.29304,3.35793 2.29304,5.48032 v 0.12745 c 0,2.16876 -0.76364,3.97621 -2.29304,5.4203 -1.5315,1.52834 -3.33791,2.29619 -5.42028,2.29619 h -9.56398 v 37.80405 c 0,1.23446 0.42343,2.27724 1.27554,3.12514 0.85002,0.85212 1.9128,1.27555 3.1873,1.27555 h 5.10114 c 2.08237,0 3.88772,0.76574 5.42028,2.29619 1.5294,1.53045 2.29304,3.35898 2.29304,5.48243 v 0.12954 c 0,2.16664 -0.76364,3.97199 -2.29304,5.41711 C 919.1923,207.23635 917.38589,208 915.30352,208 h -5.10114 c -5.52563,0 -10.26442,-1.95387 -14.21746,-5.86478 -3.91196,-3.95198 -5.86479,-8.67078 -5.86479,-14.15532 v -71.85095 c 0,-2.12558 0.7647,-3.9541 2.29515,-5.48245 1.53045,-1.48725 3.33686,-2.23193 5.41924,-2.23193 h 0.18959 z" /> + <path d="m 951.70877,150.17375 c -1.87066,0 -3.44324,0.6362 -4.71773,1.9107 -1.27556,1.31872 -1.91281,2.89025 -1.91281,4.71773 0,2.5511 1.23238,4.46389 3.69711,5.73733 0.8521,0.46872 4.40067,1.53045 10.64886,3.18834 5.05691,1.44619 8.81825,3.33686 11.28402,5.67413 3.52751,3.35898 5.2918,8.14097 5.2918,14.34703 0,6.11862 -2.16876,11.36829 -6.5031,15.74581 -4.37752,4.33644 -9.62822,6.50308 -15.74789,6.50308 h -16.64007 c -2.08342,0 -3.88879,-0.76365 -5.42028,-2.29621 -1.53045,-1.44407 -2.2941,-3.25048 -2.2941,-5.41712 v -0.12953 c 0,-2.12345 0.76365,-3.95198 2.2941,-5.48243 1.53045,-1.53045 3.33686,-2.29619 5.42028,-2.29619 h 17.2773 c 1.86962,0 3.4443,-0.6362 4.71775,-1.9107 1.27554,-1.27554 1.91279,-2.84707 1.91279,-4.71774 0,-2.33937 -1.2113,-4.10366 -3.63283,-5.29073 -0.63936,-0.30018 -4.209,-1.36192 -10.71208,-3.18834 -5.05901,-1.48725 -8.8214,-3.44535 -11.28613,-5.8669 -3.52856,-3.44217 -5.29073,-8.30949 -5.29073,-14.5998 0,-6.12073 2.16875,-11.34721 6.50413,-15.68261 4.37647,-4.37752 9.62718,-6.56839 15.74791,-6.56839 h 11.73063 c 2.1266,0 3.95304,0.76575 5.48243,2.29831 1.53045,1.52834 2.29514,3.35793 2.29514,5.48032 v 0.12745 c 0,2.16876 -0.76469,3.97622 -2.29514,5.4203 -1.52939,1.52834 -3.35687,2.29619 -5.48243,2.29619 z" /> + </g> + </g> +</svg> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/icon.svg b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/icon.svg new file mode 100644 index 00000000000..cb760940aa1 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/icon.svg @@ -0,0 +1,15 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"> + <linearGradient id="w" x1="0" y1="0" x2="0.6667" y2="0.6667"> + <stop offset="0%" stop-color="#ffe873" /> + <stop offset="100%" stop-color="#ffd43b" /> + </linearGradient> + <linearGradient id="s" x1="0" y1="0" x2="0.6667" y2="0.6667"> + <stop offset="0%" stop-color="#5a9fd4" /> + <stop offset="100%" stop-color="#306998" /> + </linearGradient> + <g> + <path fill="url(#w)" d="m 151.60708,154.81618 c -0.43704,0.0747 -0.88656,0.12978 -1.35572,0.14933 -2.45813,0.0764 -4.25357,-0.58665 -5.82335,-2.15107 l -8.89246,-8.85942 -11.23464,-11.19805 -36.040757,-35.919452 c -3.43568,-3.42217 -7.332485,-5.347474 -11.589626,-5.723468 -2.229803,-0.198219 -4.473877,0.03111 -6.640354,0.675545 -3.242133,0.944875 -6.135526,2.664848 -8.593662,5.116366 -3.834369,3.819499 -5.86349,8.414979 -5.875977,13.287799 -0.06065,4.95281 1.951523,9.60074 5.808192,13.44424 l 55.622894,55.43648 c 1.82219,1.84175 2.65971,3.79549 2.63384,6.14568 l 0.004,0.208 c 0.0527,2.43196 -0.75991,4.34571 -2.6267,6.20612 -1.78028,1.77598 -3.8094,2.65241 -6.30945,2.75552 -2.45814,0.0764 -4.25446,-0.58844 -5.82514,-2.15286 L 48.702551,136.2618 c -5.214172,-5.19459 -11.702899,-6.98745 -18.22998,-5.04881 -3.245701,0.9431 -6.135527,2.66307 -8.595446,5.11459 -3.83437,3.82127 -5.865275,8.41676 -5.875978,13.28957 -0.05619,4.95281 1.951524,9.60252 5.806409,13.4478 l 58.10689,57.90577 c 8.319842,8.29143 19.340421,11.9376 32.743314,10.83806 12.57967,-1.02043 23.02317,-5.5848 31.03441,-13.57313 7.51265,-7.4861 11.96423,-16.35175 13.28695,-26.42537 10.47206,-1.68264 19.29494,-6.04524 26.27512,-13.00158 4.01364,-3.99994 7.14963,-8.3972 9.40531,-13.16157 -14.15569,-0.39911 -28.23645,-4.00972 -41.05247,-10.83095 z" /> + <path fill="url(#s)" d="m 196.96038,146.11854 c 0.10259,-12.84514 -4.43017,-23.98541 -13.50635,-33.1346 L 147.57292,77.225374 c -0.24349,-0.240885 -0.46469,-0.487992 -0.68678,-0.744877 -1.48416,-1.739529 -2.18788,-3.583056 -2.21018,-5.807022 -0.0259,-2.470184 0.84911,-4.508375 2.7605,-6.407902 1.91406,-1.909304 3.8531,-2.737735 6.36564,-2.684403 2.53662,0.024 4.62728,0.943097 6.57257,2.881734 l 60.59178,60.384846 12.11408,-12.06914 c 1.12203,-0.90755 1.95777,-1.76887 2.87823,-2.93418 5.91879,-7.51544 5.26947,-18.272609 -1.51003,-25.02895 L 187.20456,37.727314 c -9.19393,-9.157192 -20.36703,-13.776677 -33.16789,-13.7269 -12.94266,-0.05067 -24.14163,4.548375 -33.28739,13.662901 -9.02892,8.996307 -13.64015,19.93925 -13.7008,32.487501 l -0.004,0.14222 c -0.002,0.167998 -0.005,0.336884 -0.005,0.506659 -0.091,12.232701 4.10729,22.95787 12.48154,31.881285 0.40226,0.43022 0.80274,0.85777 1.22283,1.27821 l 35.75088,35.62612 c 1.88909,1.88174 2.71769,3.79638 2.69361,6.20968 l 0.003,0.20977 c 0.0527,2.43197 -0.76081,4.34571 -2.6276,6.20791 -1.44759,1.43909 -3.06286,2.27818 -4.9564,2.60262 12.81601,6.82123 26.89677,10.43184 41.05246,10.83362 2.80598,-5.92525 4.2509,-12.41848 4.29906,-19.43526 z" /> + <path fill="#ffffff" d="m 215.68093,93.181574 c 2.84701,-2.838179 7.46359,-2.836401 10.30883,0 2.84433,2.834623 2.84612,7.435446 -0.002,10.270956 -2.84345,2.83818 -7.46271,2.83818 -10.30704,0 -2.84524,-2.83551 -2.84791,-7.435444 0,-10.270956 z" /> + </g> +</svg> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/old.svg b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/old.svg new file mode 100644 index 00000000000..a073139e331 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/old.svg @@ -0,0 +1,14 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="360" height="120" viewBox="0 0 21 7"> + <linearGradient id="w" x1="0" y1="0" x2="1" y2="1"> + <stop offset="0%" stop-color="#5a9fd4" /> + <stop offset="100%" stop-color="#306998" /> + </linearGradient> + <linearGradient id="s" x1="0" y1="0" x2="1" y2="1"> + <stop offset="0%" stop-color="#ffe873" /> + <stop offset="100%" stop-color="#ffd43b" /> + </linearGradient> + <polyline fill="none" stroke="url(#w)" stroke-linecap="round" stroke-linejoin="round" + points="1,1 1,5 5,5 5,1 5,5 9,5 9,1"/> + <polyline fill="none" stroke="url(#s)" stroke-linecap="round" stroke-linejoin="round" + points="19,1 11,1 11,3 19,3 19,5 11,5"/> +</svg> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/tidelift.png b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/tidelift.png Binary files differnew file mode 100644 index 00000000000..317dc4d9852 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/tidelift.png diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/vertical.svg b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/vertical.svg new file mode 100644 index 00000000000..b07fb223873 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/logo/vertical.svg @@ -0,0 +1,31 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="480" height="320" viewBox="0 0 480 320"> + <linearGradient id="w" x1="0.2333" y1="0" x2="0.5889" y2="0.5333"> + <stop offset="0%" stop-color="#ffe873" /> + <stop offset="100%" stop-color="#ffd43b" /> + </linearGradient> + <linearGradient id="s" x1="0.2333" y1="0" x2="0.5889" y2="0.5333"> + <stop offset="0%" stop-color="#5a9fd4" /> + <stop offset="100%" stop-color="#306998" /> + </linearGradient> + <g> + <path fill="url(#w)" d="m 263.40708,146.81618 c -0.43704,0.0747 -0.88656,0.12978 -1.35572,0.14933 -2.45813,0.0764 -4.25357,-0.58665 -5.82335,-2.15107 l -8.89246,-8.85942 -11.23464,-11.19805 -36.04076,-35.919454 c -3.43568,-3.42217 -7.33248,-5.347474 -11.58962,-5.723468 -2.22981,-0.198219 -4.47388,0.03111 -6.64036,0.675545 -3.24213,0.944875 -6.13552,2.664848 -8.59366,5.116366 -3.83437,3.819499 -5.86349,8.414979 -5.87598,13.287801 -0.0607,4.95281 1.95153,9.60074 5.8082,13.44424 l 55.62289,55.43648 c 1.82219,1.84175 2.65971,3.79549 2.63384,6.14568 l 0.004,0.208 c 0.0527,2.43196 -0.75991,4.34571 -2.6267,6.20612 -1.78028,1.77598 -3.8094,2.65241 -6.30945,2.75552 -2.45814,0.0764 -4.25446,-0.58844 -5.82514,-2.15286 L 160.50255,128.2618 c -5.21417,-5.19459 -11.7029,-6.98745 -18.22998,-5.04881 -3.2457,0.9431 -6.13553,2.66307 -8.59545,5.11459 -3.83437,3.82127 -5.86527,8.41676 -5.87597,13.28957 -0.0562,4.95281 1.95152,9.60252 5.80641,13.4478 l 58.10689,57.90577 c 8.31984,8.29143 19.34042,11.9376 32.74331,10.83806 12.57967,-1.02043 23.02317,-5.5848 31.03441,-13.57313 7.51265,-7.4861 11.96423,-16.35175 13.28695,-26.42537 10.47206,-1.68264 19.29494,-6.04524 26.27512,-13.00158 4.01364,-3.99994 7.14963,-8.3972 9.40531,-13.16157 -14.15569,-0.39911 -28.23645,-4.00972 -41.05247,-10.83095 z" /> + <path fill="url(#s)" d="m 308.76038,138.11854 c 0.10259,-12.84514 -4.43017,-23.98541 -13.50635,-33.1346 L 259.37292,69.225372 c -0.24349,-0.240885 -0.46469,-0.487992 -0.68678,-0.744877 -1.48416,-1.739529 -2.18788,-3.583056 -2.21018,-5.807022 -0.0259,-2.470184 0.84911,-4.508375 2.7605,-6.407902 1.91406,-1.909304 3.8531,-2.737735 6.36564,-2.684403 2.53662,0.024 4.62728,0.943097 6.57257,2.881734 l 60.59178,60.384848 12.11408,-12.06914 c 1.12203,-0.90755 1.95777,-1.76887 2.87823,-2.93418 5.91879,-7.515442 5.26947,-18.272611 -1.51003,-25.028952 L 299.00456,29.727312 c -9.19393,-9.157192 -20.36703,-13.776677 -33.16789,-13.7269 -12.94266,-0.05067 -24.14163,4.548375 -33.28739,13.662901 -9.02892,8.996307 -13.64015,19.93925 -13.7008,32.487501 l -0.004,0.14222 c -0.002,0.167998 -0.005,0.336884 -0.005,0.506659 -0.091,12.232701 4.10729,22.95787 12.48154,31.881285 0.40226,0.43022 0.80274,0.85777 1.22283,1.27821 l 35.75088,35.626122 c 1.88909,1.88174 2.71769,3.79638 2.69361,6.20968 l 0.003,0.20977 c 0.0527,2.43197 -0.76081,4.34571 -2.6276,6.20791 -1.44759,1.43909 -3.06286,2.27818 -4.9564,2.60262 12.81601,6.82123 26.89677,10.43184 41.05246,10.83362 2.80598,-5.92525 4.2509,-12.41848 4.29906,-19.43526 z" /> + <path fill="#ffffff" d="m 327.48093,85.181572 c 2.84701,-2.838179 7.46359,-2.836401 10.30883,0 2.84433,2.834623 2.84612,7.435446 -0.002,10.270956 -2.84345,2.83818 -7.46271,2.83818 -10.30704,0 -2.84524,-2.83551 -2.84791,-7.435444 0,-10.270956 z" /> + </g> + <g> + <g fill="#ffd43b"> + <path d="m 25.719398,284.91839 c 0,2.59075 0.912299,4.79875 2.736898,6.62269 1.824599,1.82657 4.033255,2.73821 6.625313,2.73821 2.591402,0 4.800058,-0.91164 6.624002,-2.73821 1.825254,-1.82394 2.738209,-4.03194 2.738209,-6.62269 v -21.77984 c 0,-1.32126 0.475811,-2.45901 1.42809,-3.40998 0.952278,-0.95359 2.089375,-1.43006 3.411947,-1.43006 h 0.0793 c 1.348132,0 2.471467,0.47647 3.371969,1.43006 0.952278,0.95097 1.428745,2.08938 1.428745,3.40998 v 21.77984 c 0,2.59075 0.912299,4.79875 2.738209,6.62269 1.823944,1.82657 4.031289,2.73821 6.624002,2.73821 2.618274,0 4.839382,-0.91164 6.663981,-2.73821 1.825254,-1.82394 2.738209,-4.03194 2.738209,-6.62269 v -21.77984 c 0,-1.32126 0.475156,-2.45901 1.42809,-3.40998 0.897881,-0.95359 2.022526,-1.43006 3.371969,-1.43006 h 0.07865 c 1.323228,0 2.460325,0.47647 3.411948,1.43006 0.926062,0.95097 1.388766,2.08938 1.388766,3.40998 v 21.77984 c 0,5.26211 -1.865233,9.75807 -5.593077,13.48657 -3.729156,3.7285 -8.22577,5.59373 -13.487876,5.59373 -6.294998,0 -11.028207,-2.08807 -14.202904,-6.26747 -3.199602,4.1794 -7.94723,6.26747 -14.240916,6.26747 -5.262763,0 -9.759377,-1.86523 -13.487876,-5.59373 C 17.866544,294.67646 16,290.18115 16,284.91839 v -21.77984 c 0,-1.32126 0.476467,-2.45901 1.428745,-3.40998 0.951623,-0.95359 2.075612,-1.43006 3.371969,-1.43006 h 0.11928 c 1.295702,0 2.419036,0.47647 3.372625,1.43006 0.950967,0.95097 1.427434,2.08938 1.427434,3.40998 v 21.77984 z" /> + <path d="m 132.94801,271.6291 c 0.31786,0.66063 0.47712,1.33371 0.47712,2.02252 0,0.55577 -0.10551,1.11089 -0.3172,1.66665 -0.45026,1.24262 -1.29636,2.14181 -2.53898,2.69692 -3.70293,1.66665 -8.56853,3.8622 -14.59875,6.58599 -7.48453,3.38442 -11.87497,5.38139 -13.17067,5.9909 2.00942,2.53832 5.14414,3.80715 9.40219,3.80715 2.82931,0 5.39515,-0.83234 7.69556,-2.499 2.24798,-1.63977 3.82222,-3.75537 4.72141,-6.34808 0.76746,-2.16868 2.30107,-3.25269 4.60148,-3.25269 1.63912,0 2.94859,0.68881 3.92708,2.06185 0.6082,0.84742 0.9123,1.7335 0.9123,2.65891 0,0.55577 -0.10552,1.12399 -0.31655,1.70532 -1.56048,4.52348 -4.29869,8.17334 -8.21135,10.95087 -3.96706,2.88108 -8.41059,4.32293 -13.32993,4.32293 -6.29434,0 -11.67639,-2.23356 -16.145474,-6.70395 -4.469743,-4.46975 -6.704615,-9.85114 -6.704615,-16.14679 0,-6.29434 2.234872,-11.67507 6.704615,-16.14678 4.468434,-4.46843 9.851134,-6.70396 16.145474,-6.70396 4.54773,0 8.70027,1.24392 12.45629,3.7285 3.72785,2.43607 6.49162,5.63437 8.29,9.60274 z m -20.74695,-3.5332 c -3.64985,0 -6.7577,1.28391 -9.32289,3.84909 -2.53897,2.5665 -3.808452,5.67435 -3.808452,9.32289 v 0.27789 l 22.175692,-9.95731 c -1.95633,-2.32597 -4.97177,-3.49256 -9.04435,-3.49256 z" /> + <path d="m 146.11999,242.03442 c 1.2957,0 2.41904,0.46336 3.37197,1.38876 0.95228,0.95097 1.42874,2.08938 1.42874,3.4113 v 15.4311 c 2.98792,-2.64318 7.36525,-3.96707 13.13004,-3.96707 6.29434,0 11.67638,2.23488 16.14613,6.70396 4.46908,4.47106 6.70461,9.85245 6.70461,16.14679 0,6.29499 -2.23553,11.67638 -6.70461,16.14678 -4.46909,4.4704 -9.85113,6.70396 -16.14613,6.70396 -6.295,0 -11.66262,-2.22111 -16.10549,-6.66529 -4.4704,-4.41469 -6.71838,-9.77052 -6.7446,-16.06617 v -34.43341 c 0,-1.32257 0.47647,-2.46032 1.42875,-3.41129 0.95162,-0.92541 2.07561,-1.38877 3.37197,-1.38877 h 0.11862 z m 17.93009,26.06148 c -3.64919,0 -6.75704,1.28391 -9.32288,3.84909 -2.53767,2.5665 -3.80781,5.67435 -3.80781,9.32289 0,3.62364 1.27014,6.71772 3.80781,9.28291 2.56584,2.56519 5.67303,3.84778 9.32288,3.84778 3.62364,0 6.71773,-1.28259 9.28357,-3.84778 2.56387,-2.56519 3.84712,-5.65927 3.84712,-9.28291 0,-3.64788 -1.28325,-6.75639 -3.84712,-9.32289 -2.56584,-2.56518 -5.65927,-3.84909 -9.28357,-3.84909 z" /> + </g> + <g fill="#306998"> + <path d="m 205.94246,268.01922 c -1.16397,0 -2.14247,0.39586 -2.93548,1.18888 -0.79368,0.82054 -1.19019,1.79838 -1.19019,2.93548 0,1.58735 0.76681,2.77753 2.30172,3.56989 0.52825,0.29165 2.7369,0.95228 6.62466,1.98386 3.14717,0.89985 5.48691,2.07627 7.02051,3.53057 2.19621,2.09003 3.29267,5.06549 3.29267,8.92704 0,3.80714 -1.34879,7.0736 -4.04571,9.79739 -2.72444,2.69823 -5.9909,4.04636 -9.7987,4.04636 h -10.35381 c -1.29701,0 -2.41969,-0.47516 -3.37262,-1.42875 -0.95228,-0.89853 -1.42875,-2.02252 -1.42875,-3.37065 v -0.0806 c 0,-1.32126 0.47647,-2.45901 1.42875,-3.41129 0.95227,-0.95228 2.07561,-1.42874 3.37262,-1.42874 h 10.75032 c 1.16331,0 2.14246,-0.39586 2.93548,-1.18888 0.79368,-0.79367 1.19019,-1.77151 1.19019,-2.93548 0,-1.45561 -0.7537,-2.55339 -2.26044,-3.29201 -0.3965,-0.18678 -2.61892,-0.84742 -6.66529,-1.98386 -3.14782,-0.9254 -5.48887,-2.14377 -7.02247,-3.65051 -2.19555,-2.1418 -3.29202,-5.17035 -3.29202,-9.08432 0,-3.80846 1.34945,-7.06049 4.04702,-9.75807 2.72314,-2.72379 5.99024,-4.087 9.79805,-4.087 h 7.2997 c 1.32192,0 2.45967,0.47647 3.41195,1.43006 0.95162,0.95097 1.42809,2.08938 1.42809,3.40998 v 0.0793 c 0,1.34945 -0.47647,2.47409 -1.42809,3.37263 -0.95228,0.95097 -2.09003,1.42874 -3.41195,1.42874 z" /> + <path d="m 249.06434,258.29851 c 6.29434,0 11.67573,2.23488 16.14612,6.70396 4.46909,4.47106 6.70396,9.85245 6.70396,16.14679 0,6.29499 -2.23487,11.67638 -6.70396,16.14678 -4.46974,4.46974 -9.85178,6.70396 -16.14612,6.70396 -6.29435,0 -11.67639,-2.23356 -16.14548,-6.70396 -4.46974,-4.46974 -6.70461,-9.85113 -6.70461,-16.14678 0,-6.29434 2.23487,-11.67508 6.70461,-16.14679 4.46909,-4.46908 9.85113,-6.70396 16.14548,-6.70396 z m 0,9.79739 c -3.64986,0 -6.7577,1.28391 -9.32289,3.84909 -2.53963,2.5665 -3.80911,5.67435 -3.80911,9.32289 0,3.62364 1.26948,6.71772 3.80911,9.28291 2.56519,2.56519 5.67238,3.84778 9.32289,3.84778 3.62298,0 6.71706,-1.28259 9.28291,-3.84778 2.56518,-2.56519 3.84778,-5.65927 3.84778,-9.28291 0,-3.64788 -1.2826,-6.75639 -3.84778,-9.32289 -2.56585,-2.56518 -5.65928,-3.84909 -9.28291,-3.84909 z" /> + <path d="m 307.22146,259.37007 c 2.24864,0.71438 3.37263,2.24798 3.37263,4.60148 v 0.19989 c 0,1.6116 -0.64884,2.89419 -1.94454,3.84778 -0.89919,0.63376 -1.82525,0.95097 -2.77622,0.95097 -0.50334,0 -1.01913,-0.0793 -1.54737,-0.23791 -1.29636,-0.42272 -2.63204,-0.63638 -4.00638,-0.63638 -3.64986,0 -6.75836,1.28391 -9.32289,3.84909 -2.53963,2.5665 -3.80846,5.67435 -3.80846,9.32289 0,3.62364 1.26883,6.71772 3.80846,9.28291 2.56453,2.56519 5.67238,3.84778 9.32289,3.84778 1.375,0 2.71068,-0.21103 4.00638,-0.63507 0.50203,-0.1586 1.00471,-0.2379 1.50739,-0.2379 0.97718,0 1.91767,0.31851 2.81686,0.95358 1.2957,0.95097 1.94453,2.24798 1.94453,3.88776 0,2.32728 -1.12464,3.86089 -3.37262,4.60148 -2.22111,0.6875 -4.52152,1.03027 -6.90189,1.03027 -6.29434,0 -11.67638,-2.23356 -16.14678,-6.70396 -4.46843,-4.46974 -6.70396,-9.85113 -6.70396,-16.14678 0,-6.29435 2.23487,-11.67508 6.70396,-16.14679 4.46974,-4.46843 9.85178,-6.70396 16.14678,-6.70396 2.37906,0.001 4.68012,0.35981 6.90123,1.07287 z" /> + <path d="m 322.25671,242.03442 c 1.29504,0 2.41903,0.46336 3.37262,1.38876 0.95163,0.95097 1.42809,2.08938 1.42809,3.4113 v 27.49154 h 1.50739 c 3.38508,0 6.33301,-1.12399 8.84708,-3.37263 2.45901,-2.24798 3.86023,-5.0242 4.20431,-8.33063 0.15861,-1.24261 0.68816,-2.26174 1.58735,-3.0541 0.89854,-0.84611 1.96944,-1.27015 3.21271,-1.27015 h 0.11863 c 1.40252,0 2.5796,0.53021 3.53122,1.58735 0.84676,0.92541 1.26949,1.99697 1.26949,3.21271 0,0.15861 -0.0138,0.33163 -0.0393,0.51579 -0.63507,6.63842 -3.17405,11.61019 -7.61692,14.91531 2.32663,1.43006 4.46909,3.84909 6.42739,7.26039 2.03563,3.51746 3.05476,7.31412 3.05476,11.38473 v 2.02515 c 0,1.34813 -0.47712,2.47147 -1.42809,3.37066 -0.95359,0.95359 -2.07692,1.42874 -3.37263,1.42874 h -0.11928 c -1.29635,0 -2.41969,-0.47515 -3.37196,-1.42874 -0.95228,-0.89854 -1.42809,-2.02253 -1.42809,-3.37066 v -2.02515 c -0.0275,-3.59414 -1.31012,-6.67708 -3.84844,-9.24358 -2.56584,-2.53832 -5.66058,-3.80715 -9.28291,-3.80715 h -3.25269 v 15.07523 c 0,1.34813 -0.47646,2.47146 -1.42809,3.37065 -0.95293,0.95359 -2.07758,1.42875 -3.37262,1.42875 h -0.12059 c -1.2957,0 -2.41838,-0.47516 -3.37132,-1.42875 -0.95162,-0.89853 -1.42809,-2.02252 -1.42809,-3.37065 v -52.36547 c 0,-1.32257 0.47647,-2.46032 1.42809,-3.41129 0.95228,-0.92541 2.07562,-1.38877 3.37132,-1.38877 h 0.12059 z" /> + <path d="m 402.31164,271.6291 c 0.31721,0.66063 0.47581,1.33371 0.47581,2.02252 0,0.55577 -0.10617,1.11089 -0.31655,1.66665 -0.45025,1.24262 -1.29635,2.14181 -2.53897,2.69692 -3.70294,1.66665 -8.56919,3.8622 -14.59876,6.58599 -7.48452,3.38442 -11.87496,5.38139 -13.17067,5.9909 2.00877,2.53832 5.14349,3.80715 9.40219,3.80715 2.82866,0 5.3945,-0.83234 7.69622,-2.499 2.24732,-1.63977 3.82091,-3.75537 4.7201,-6.34808 0.76681,-2.16868 2.30172,-3.25269 4.60148,-3.25269 1.63978,0 2.94924,0.68881 3.92839,2.06185 0.60689,0.84742 0.91165,1.7335 0.91165,2.65891 0,0.55577 -0.10552,1.12399 -0.31721,1.70532 -1.56048,4.52348 -4.29738,8.17334 -8.21135,10.95087 -3.96706,2.88108 -8.40994,4.32293 -13.32928,4.32293 -6.29434,0 -11.67638,-2.23356 -16.14547,-6.70395 -4.46974,-4.46975 -6.70461,-9.85114 -6.70461,-16.14679 0,-6.29434 2.23487,-11.67507 6.70461,-16.14678 4.46843,-4.46843 9.85113,-6.70396 16.14547,-6.70396 4.54774,0 8.70093,1.24392 12.4563,3.7285 3.7285,2.43607 6.49161,5.63437 8.29065,9.60274 z m -20.7476,-3.5332 c -3.6492,0 -6.7577,1.28391 -9.32289,3.84909 -2.53897,2.5665 -3.80846,5.67435 -3.80846,9.32289 v 0.27789 l 22.1757,-9.95731 c -1.95699,-2.32597 -4.97177,-3.49256 -9.04435,-3.49256 z" /> + <path d="m 415.48166,242.03442 c 1.2957,0 2.41969,0.46336 3.37262,1.38876 0.95162,0.95097 1.42809,2.08938 1.42809,3.4113 v 11.46403 h 5.95092 c 1.2957,0 2.41903,0.47647 3.37262,1.43006 0.95163,0.95097 1.42678,2.08938 1.42678,3.40998 v 0.0793 c 0,1.34945 -0.47515,2.47409 -1.42678,3.37263 -0.95293,0.95097 -2.07692,1.42874 -3.37262,1.42874 h -5.95092 v 23.52252 c 0,0.76811 0.26347,1.41695 0.79367,1.94453 0.5289,0.53021 1.19019,0.79368 1.98321,0.79368 h 3.17404 c 1.2957,0 2.41903,0.47646 3.37262,1.42874 0.95163,0.95228 1.42678,2.09003 1.42678,3.41129 v 0.0806 c 0,1.34813 -0.47515,2.47146 -1.42678,3.37065 C 428.65298,303.52484 427.52899,304 426.23329,304 h -3.17404 c -3.43817,0 -6.38675,-1.21574 -8.84642,-3.6492 -2.43411,-2.45901 -3.6492,-5.39515 -3.6492,-8.80775 v -44.70726 c 0,-1.32258 0.47581,-2.46033 1.42809,-3.4113 0.95228,-0.9254 2.07627,-1.38876 3.37197,-1.38876 h 0.11797 z" /> + <path d="m 448.88545,268.01922 c -1.16397,0 -2.14246,0.39586 -2.93548,1.18888 -0.79368,0.82054 -1.19019,1.79838 -1.19019,2.93548 0,1.58735 0.76681,2.77753 2.30042,3.56989 0.5302,0.29165 2.7382,0.95228 6.62596,1.98386 3.14652,0.89985 5.48691,2.07627 7.02117,3.53057 2.19489,2.09003 3.29267,5.06549 3.29267,8.92704 0,3.80714 -1.34945,7.0736 -4.04637,9.79739 -2.72379,2.69823 -5.99089,4.04636 -9.79869,4.04636 h -10.35382 c -1.29635,0 -2.41969,-0.47516 -3.37262,-1.42875 -0.95228,-0.89853 -1.42744,-2.02252 -1.42744,-3.37065 v -0.0806 c 0,-1.32126 0.47516,-2.45901 1.42744,-3.41129 0.95228,-0.95228 2.07627,-1.42874 3.37262,-1.42874 h 10.75032 c 1.16332,0 2.14312,-0.39586 2.93549,-1.18888 0.79367,-0.79367 1.19018,-1.77151 1.19018,-2.93548 0,-1.45561 -0.7537,-2.55339 -2.26043,-3.29201 -0.39782,-0.18678 -2.61893,-0.84742 -6.66529,-1.98386 -3.14783,-0.9254 -5.48887,-2.14377 -7.02248,-3.65051 -2.19555,-2.1418 -3.29201,-5.17035 -3.29201,-9.08432 0,-3.80846 1.34944,-7.06049 4.04701,-9.75807 2.72314,-2.72379 5.99025,-4.087 9.7987,-4.087 h 7.29906 c 1.32322,0 2.45967,0.47647 3.41129,1.43006 0.95228,0.95097 1.42809,2.08938 1.42809,3.40998 v 0.0793 c 0,1.34945 -0.47581,2.47409 -1.42809,3.37263 -0.95162,0.95097 -2.08872,1.42874 -3.41129,1.42874 z" /> + </g> + </g> +</svg> diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/performance/mem_client.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/performance/mem_client.py new file mode 100644 index 00000000000..890216edf80 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/performance/mem_client.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import asyncio +import statistics +import tracemalloc + +import websockets +from websockets.extensions import permessage_deflate + + +CLIENTS = 10 +INTERVAL = 1 / 10 # seconds + +MEM_SIZE = [] + + +async def mem_client(client): + # Space out connections to make them sequential. + await asyncio.sleep(client * INTERVAL) + + tracemalloc.start() + + async with websockets.connect( + "ws://localhost:8765", + extensions=[ + permessage_deflate.ClientPerMessageDeflateFactory( + server_max_window_bits=10, + client_max_window_bits=10, + compress_settings={"memLevel": 3}, + ) + ], + ) as ws: + await ws.send("hello") + await ws.recv() + + await ws.send(b"hello") + await ws.recv() + + MEM_SIZE.append(tracemalloc.get_traced_memory()[0]) + tracemalloc.stop() + + # Hold connection open until the end of the test. + await asyncio.sleep(CLIENTS * INTERVAL) + + +asyncio.get_event_loop().run_until_complete( + asyncio.gather(*[mem_client(client) for client in range(CLIENTS + 1)]) +) + +# First connection incurs non-representative setup costs. +del MEM_SIZE[0] + +print(f"µ = {statistics.mean(MEM_SIZE) / 1024:.1f} KiB") +print(f"σ = {statistics.stdev(MEM_SIZE) / 1024:.1f} KiB") diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/performance/mem_server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/performance/mem_server.py new file mode 100644 index 00000000000..0a4a29f76c6 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/performance/mem_server.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +import asyncio +import signal +import statistics +import tracemalloc + +import websockets +from websockets.extensions import permessage_deflate + + +CLIENTS = 10 +INTERVAL = 1 / 10 # seconds + +MEM_SIZE = [] + + +async def handler(ws, path): + msg = await ws.recv() + await ws.send(msg) + + msg = await ws.recv() + await ws.send(msg) + + MEM_SIZE.append(tracemalloc.get_traced_memory()[0]) + tracemalloc.stop() + + tracemalloc.start() + + # Hold connection open until the end of the test. + await asyncio.sleep(CLIENTS * INTERVAL) + + +async def mem_server(stop): + async with websockets.serve( + handler, + "localhost", + 8765, + extensions=[ + permessage_deflate.ServerPerMessageDeflateFactory( + server_max_window_bits=10, + client_max_window_bits=10, + compress_settings={"memLevel": 3}, + ) + ], + ): + await stop + + +loop = asyncio.get_event_loop() + +stop = loop.create_future() +loop.add_signal_handler(signal.SIGINT, stop.set_result, None) + +tracemalloc.start() + +loop.run_until_complete(mem_server(stop)) + +# First connection incurs non-representative setup costs. +del MEM_SIZE[0] + +print(f"µ = {statistics.mean(MEM_SIZE) / 1024:.1f} KiB") +print(f"σ = {statistics.stdev(MEM_SIZE) / 1024:.1f} KiB") diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/setup.cfg b/tests/wpt/web-platform-tests/tools/third_party/websockets/setup.cfg new file mode 100644 index 00000000000..c306b2d4fb7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/setup.cfg @@ -0,0 +1,30 @@ +[bdist_wheel] +python-tag = py36.py37 + +[metadata] +license_file = LICENSE + +[flake8] +ignore = E731,F403,F405,W503 +max-line-length = 88 + +[isort] +combine_as_imports = True +force_grid_wrap = 0 +include_trailing_comma = True +known_standard_library = asyncio +line_length = 88 +lines_after_imports = 2 +multi_line_output = 3 + +[coverage:run] +branch = True +omit = */__main__.py +source = + websockets + tests + +[coverage:paths] +source = + src/websockets + .tox/*/lib/python*/site-packages/websockets diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/setup.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/setup.py new file mode 100644 index 00000000000..f3581924770 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/setup.py @@ -0,0 +1,66 @@ +import pathlib +import re +import sys + +import setuptools + + +root_dir = pathlib.Path(__file__).parent + +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" + +long_description = (root_dir / 'README.rst').read_text(encoding='utf-8') + +# PyPI disables the "raw" directive. +long_description = re.sub( + r"^\.\. raw:: html.*?^(?=\w)", + "", + long_description, + flags=re.DOTALL | re.MULTILINE, +) + +exec((root_dir / 'src' / 'websockets' / 'version.py').read_text(encoding='utf-8')) + +if sys.version_info[:3] < (3, 6, 1): + raise Exception("websockets requires Python >= 3.6.1.") + +packages = ['websockets', 'websockets/extensions'] + +ext_modules = [ + setuptools.Extension( + 'websockets.speedups', + sources=['src/websockets/speedups.c'], + optional=not (root_dir / '.cibuildwheel').exists(), + ) +] + +setuptools.setup( + name='websockets', + version=version, + description=description, + long_description=long_description, + url='https://github.com/aaugustin/websockets', + author='Aymeric Augustin', + author_email='aymeric.augustin@m4x.org', + license='BSD', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Web Environment', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + ], + package_dir = {'': 'src'}, + package_data = {'websockets': ['py.typed']}, + packages=packages, + ext_modules=ext_modules, + include_package_data=True, + zip_safe=False, + python_requires='>=3.6.1', + test_loader='unittest:TestLoader', +) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py new file mode 100644 index 00000000000..ea1d829a335 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/__init__.py @@ -0,0 +1,55 @@ +# This relies on each of the submodules having an __all__ variable. + +from .auth import * # noqa +from .client import * # noqa +from .exceptions import * # noqa +from .protocol import * # noqa +from .server import * # noqa +from .typing import * # noqa +from .uri import * # noqa +from .version import version as __version__ # noqa + + +__all__ = [ + "AbortHandshake", + "basic_auth_protocol_factory", + "BasicAuthWebSocketServerProtocol", + "connect", + "ConnectionClosed", + "ConnectionClosedError", + "ConnectionClosedOK", + "Data", + "DuplicateParameter", + "ExtensionHeader", + "ExtensionParameter", + "InvalidHandshake", + "InvalidHeader", + "InvalidHeaderFormat", + "InvalidHeaderValue", + "InvalidMessage", + "InvalidOrigin", + "InvalidParameterName", + "InvalidParameterValue", + "InvalidState", + "InvalidStatusCode", + "InvalidUpgrade", + "InvalidURI", + "NegotiationError", + "Origin", + "parse_uri", + "PayloadTooBig", + "ProtocolError", + "RedirectHandshake", + "SecurityError", + "serve", + "Subprotocol", + "unix_connect", + "unix_serve", + "WebSocketClientProtocol", + "WebSocketCommonProtocol", + "WebSocketException", + "WebSocketProtocolError", + "WebSocketServer", + "WebSocketServerProtocol", + "WebSocketURI", +] diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py new file mode 100644 index 00000000000..394f7ac799c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/__main__.py @@ -0,0 +1,206 @@ +import argparse +import asyncio +import os +import signal +import sys +import threading +from typing import Any, Set + +from .client import connect +from .exceptions import ConnectionClosed, format_close + + +if sys.platform == "win32": + + def win_enable_vt100() -> None: + """ + Enable VT-100 for console output on Windows. + + See also https://bugs.python.org/issue29059. + + """ + import ctypes + + STD_OUTPUT_HANDLE = ctypes.c_uint(-11) + INVALID_HANDLE_VALUE = ctypes.c_uint(-1) + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x004 + + handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) + if handle == INVALID_HANDLE_VALUE: + raise RuntimeError("unable to obtain stdout handle") + + cur_mode = ctypes.c_uint() + if ctypes.windll.kernel32.GetConsoleMode(handle, ctypes.byref(cur_mode)) == 0: + raise RuntimeError("unable to query current console mode") + + # ctypes ints lack support for the required bit-OR operation. + # Temporarily convert to Py int, do the OR and convert back. + py_int_mode = int.from_bytes(cur_mode, sys.byteorder) + new_mode = ctypes.c_uint(py_int_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) + + if ctypes.windll.kernel32.SetConsoleMode(handle, new_mode) == 0: + raise RuntimeError("unable to set console mode") + + +def exit_from_event_loop_thread( + loop: asyncio.AbstractEventLoop, stop: "asyncio.Future[None]" +) -> None: + loop.stop() + if not stop.done(): + # When exiting the thread that runs the event loop, raise + # KeyboardInterrupt in the main thread to exit the program. + try: + ctrl_c = signal.CTRL_C_EVENT # Windows + except AttributeError: + ctrl_c = signal.SIGINT # POSIX + os.kill(os.getpid(), ctrl_c) + + +def print_during_input(string: str) -> None: + sys.stdout.write( + # Save cursor position + "\N{ESC}7" + # Add a new line + "\N{LINE FEED}" + # Move cursor up + "\N{ESC}[A" + # Insert blank line, scroll last line down + "\N{ESC}[L" + # Print string in the inserted blank line + f"{string}\N{LINE FEED}" + # Restore cursor position + "\N{ESC}8" + # Move cursor down + "\N{ESC}[B" + ) + sys.stdout.flush() + + +def print_over_input(string: str) -> None: + sys.stdout.write( + # Move cursor to beginning of line + "\N{CARRIAGE RETURN}" + # Delete current line + "\N{ESC}[K" + # Print string + f"{string}\N{LINE FEED}" + ) + sys.stdout.flush() + + +async def run_client( + uri: str, + loop: asyncio.AbstractEventLoop, + inputs: "asyncio.Queue[str]", + stop: "asyncio.Future[None]", +) -> None: + try: + websocket = await connect(uri) + except Exception as exc: + print_over_input(f"Failed to connect to {uri}: {exc}.") + exit_from_event_loop_thread(loop, stop) + return + else: + print_during_input(f"Connected to {uri}.") + + try: + while True: + incoming: asyncio.Future[Any] = asyncio.ensure_future(websocket.recv()) + outgoing: asyncio.Future[Any] = asyncio.ensure_future(inputs.get()) + done: Set[asyncio.Future[Any]] + pending: Set[asyncio.Future[Any]] + done, pending = await asyncio.wait( + [incoming, outgoing, stop], return_when=asyncio.FIRST_COMPLETED + ) + + # Cancel pending tasks to avoid leaking them. + if incoming in pending: + incoming.cancel() + if outgoing in pending: + outgoing.cancel() + + if incoming in done: + try: + message = incoming.result() + except ConnectionClosed: + break + else: + if isinstance(message, str): + print_during_input("< " + message) + else: + print_during_input("< (binary) " + message.hex()) + + if outgoing in done: + message = outgoing.result() + await websocket.send(message) + + if stop in done: + break + + finally: + await websocket.close() + close_status = format_close(websocket.close_code, websocket.close_reason) + + print_over_input(f"Connection closed: {close_status}.") + + exit_from_event_loop_thread(loop, stop) + + +def main() -> None: + # If we're on Windows, enable VT100 terminal support. + if sys.platform == "win32": + try: + win_enable_vt100() + except RuntimeError as exc: + sys.stderr.write( + f"Unable to set terminal to VT100 mode. This is only " + f"supported since Win10 anniversary update. Expect " + f"weird symbols on the terminal.\nError: {exc}\n" + ) + sys.stderr.flush() + + try: + import readline # noqa + except ImportError: # Windows has no `readline` normally + pass + + # Parse command line arguments. + parser = argparse.ArgumentParser( + prog="python -m websockets", + description="Interactive WebSocket client.", + add_help=False, + ) + parser.add_argument("uri", metavar="<uri>") + args = parser.parse_args() + + # Create an event loop that will run in a background thread. + loop = asyncio.new_event_loop() + + # Create a queue of user inputs. There's no need to limit its size. + inputs: asyncio.Queue[str] = asyncio.Queue(loop=loop) + + # Create a stop condition when receiving SIGINT or SIGTERM. + stop: asyncio.Future[None] = loop.create_future() + + # Schedule the task that will manage the connection. + asyncio.ensure_future(run_client(args.uri, loop, inputs, stop), loop=loop) + + # Start the event loop in a background thread. + thread = threading.Thread(target=loop.run_forever) + thread.start() + + # Read from stdin in the main thread in order to receive signals. + try: + while True: + # Since there's no size limit, put_nowait is identical to put. + message = input("> ") + loop.call_soon_threadsafe(inputs.put_nowait, message) + except (KeyboardInterrupt, EOFError): # ^C, ^D + loop.call_soon_threadsafe(stop.set_result, None) + + # Wait for the event loop to terminate. + thread.join() + + +if __name__ == "__main__": + main() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py new file mode 100644 index 00000000000..ae204b8d9c7 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/auth.py @@ -0,0 +1,160 @@ +""" +:mod:`websockets.auth` provides HTTP Basic Authentication according to +:rfc:`7235` and :rfc:`7617`. + +""" + + +import functools +import http +from typing import Any, Awaitable, Callable, Iterable, Optional, Tuple, Type, Union + +from .exceptions import InvalidHeader +from .headers import build_www_authenticate_basic, parse_authorization_basic +from .http import Headers +from .server import HTTPResponse, WebSocketServerProtocol + + +__all__ = ["BasicAuthWebSocketServerProtocol", "basic_auth_protocol_factory"] + +Credentials = Tuple[str, str] + + +def is_credentials(value: Any) -> bool: + try: + username, password = value + except (TypeError, ValueError): + return False + else: + return isinstance(username, str) and isinstance(password, str) + + +class BasicAuthWebSocketServerProtocol(WebSocketServerProtocol): + """ + WebSocket server protocol that enforces HTTP Basic Auth. + + """ + + def __init__( + self, + *args: Any, + realm: str, + check_credentials: Callable[[str, str], Awaitable[bool]], + **kwargs: Any, + ) -> None: + self.realm = realm + self.check_credentials = check_credentials + super().__init__(*args, **kwargs) + + async def process_request( + self, path: str, request_headers: Headers + ) -> Optional[HTTPResponse]: + """ + Check HTTP Basic Auth and return a HTTP 401 or 403 response if needed. + + If authentication succeeds, the username of the authenticated user is + stored in the ``username`` attribute. + + """ + try: + authorization = request_headers["Authorization"] + except KeyError: + return ( + http.HTTPStatus.UNAUTHORIZED, + [("WWW-Authenticate", build_www_authenticate_basic(self.realm))], + b"Missing credentials\n", + ) + + try: + username, password = parse_authorization_basic(authorization) + except InvalidHeader: + return ( + http.HTTPStatus.UNAUTHORIZED, + [("WWW-Authenticate", build_www_authenticate_basic(self.realm))], + b"Unsupported credentials\n", + ) + + if not await self.check_credentials(username, password): + return ( + http.HTTPStatus.UNAUTHORIZED, + [("WWW-Authenticate", build_www_authenticate_basic(self.realm))], + b"Invalid credentials\n", + ) + + self.username = username + + return await super().process_request(path, request_headers) + + +def basic_auth_protocol_factory( + realm: str, + credentials: Optional[Union[Credentials, Iterable[Credentials]]] = None, + check_credentials: Optional[Callable[[str, str], Awaitable[bool]]] = None, + create_protocol: Type[ + BasicAuthWebSocketServerProtocol + ] = BasicAuthWebSocketServerProtocol, +) -> Callable[[Any], BasicAuthWebSocketServerProtocol]: + """ + Protocol factory that enforces HTTP Basic Auth. + + ``basic_auth_protocol_factory`` is designed to integrate with + :func:`~websockets.server.serve` like this:: + + websockets.serve( + ..., + create_protocol=websockets.basic_auth_protocol_factory( + realm="my dev server", + credentials=("hello", "iloveyou"), + ) + ) + + ``realm`` indicates the scope of protection. It should contain only ASCII + characters because the encoding of non-ASCII characters is undefined. + Refer to section 2.2 of :rfc:`7235` for details. + + ``credentials`` defines hard coded authorized credentials. It can be a + ``(username, password)`` pair or a list of such pairs. + + ``check_credentials`` defines a coroutine that checks whether credentials + are authorized. This coroutine receives ``username`` and ``password`` + arguments and returns a :class:`bool`. + + One of ``credentials`` or ``check_credentials`` must be provided but not + both. + + By default, ``basic_auth_protocol_factory`` creates a factory for building + :class:`BasicAuthWebSocketServerProtocol` instances. You can override this + with the ``create_protocol`` parameter. + + :param realm: scope of protection + :param credentials: hard coded credentials + :param check_credentials: coroutine that verifies credentials + :raises TypeError: if the credentials argument has the wrong type + + """ + if (credentials is None) == (check_credentials is None): + raise TypeError("provide either credentials or check_credentials") + + if credentials is not None: + if is_credentials(credentials): + + async def check_credentials(username: str, password: str) -> bool: + return (username, password) == credentials + + elif isinstance(credentials, Iterable): + credentials_list = list(credentials) + if all(is_credentials(item) for item in credentials_list): + credentials_dict = dict(credentials_list) + + async def check_credentials(username: str, password: str) -> bool: + return credentials_dict.get(username) == password + + else: + raise TypeError(f"invalid credentials argument: {credentials}") + + else: + raise TypeError(f"invalid credentials argument: {credentials}") + + return functools.partial( + create_protocol, realm=realm, check_credentials=check_credentials + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/client.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/client.py new file mode 100644 index 00000000000..eb58f9f4842 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/client.py @@ -0,0 +1,584 @@ +""" +:mod:`websockets.client` defines the WebSocket client APIs. + +""" + +import asyncio +import collections.abc +import functools +import logging +import warnings +from types import TracebackType +from typing import Any, Generator, List, Optional, Sequence, Tuple, Type, cast + +from .exceptions import ( + InvalidHandshake, + InvalidHeader, + InvalidMessage, + InvalidStatusCode, + NegotiationError, + RedirectHandshake, + SecurityError, +) +from .extensions.base import ClientExtensionFactory, Extension +from .extensions.permessage_deflate import ClientPerMessageDeflateFactory +from .handshake import build_request, check_response +from .headers import ( + build_authorization_basic, + build_extension, + build_subprotocol, + parse_extension, + parse_subprotocol, +) +from .http import USER_AGENT, Headers, HeadersLike, read_response +from .protocol import WebSocketCommonProtocol +from .typing import ExtensionHeader, Origin, Subprotocol +from .uri import WebSocketURI, parse_uri + + +__all__ = ["connect", "unix_connect", "WebSocketClientProtocol"] + +logger = logging.getLogger(__name__) + + +class WebSocketClientProtocol(WebSocketCommonProtocol): + """ + :class:`~asyncio.Protocol` subclass implementing a WebSocket client. + + This class inherits most of its methods from + :class:`~websockets.protocol.WebSocketCommonProtocol`. + + """ + + is_client = True + side = "client" + + def __init__( + self, + *, + origin: Optional[Origin] = None, + extensions: Optional[Sequence[ClientExtensionFactory]] = None, + subprotocols: Optional[Sequence[Subprotocol]] = None, + extra_headers: Optional[HeadersLike] = None, + **kwargs: Any, + ) -> None: + self.origin = origin + self.available_extensions = extensions + self.available_subprotocols = subprotocols + self.extra_headers = extra_headers + super().__init__(**kwargs) + + def write_http_request(self, path: str, headers: Headers) -> None: + """ + Write request line and headers to the HTTP request. + + """ + self.path = path + self.request_headers = headers + + logger.debug("%s > GET %s HTTP/1.1", self.side, path) + logger.debug("%s > %r", self.side, headers) + + # Since the path and headers only contain ASCII characters, + # we can keep this simple. + request = f"GET {path} HTTP/1.1\r\n" + request += str(headers) + + self.transport.write(request.encode()) + + async def read_http_response(self) -> Tuple[int, Headers]: + """ + Read status line and headers from the HTTP response. + + If the response contains a body, it may be read from ``self.reader`` + after this coroutine returns. + + :raises ~websockets.exceptions.InvalidMessage: if the HTTP message is + malformed or isn't an HTTP/1.1 GET response + + """ + try: + status_code, reason, headers = await read_response(self.reader) + except Exception as exc: + raise InvalidMessage("did not receive a valid HTTP response") from exc + + logger.debug("%s < HTTP/1.1 %d %s", self.side, status_code, reason) + logger.debug("%s < %r", self.side, headers) + + self.response_headers = headers + + return status_code, self.response_headers + + @staticmethod + def process_extensions( + headers: Headers, + available_extensions: Optional[Sequence[ClientExtensionFactory]], + ) -> List[Extension]: + """ + Handle the Sec-WebSocket-Extensions HTTP response header. + + Check that each extension is supported, as well as its parameters. + + Return the list of accepted extensions. + + Raise :exc:`~websockets.exceptions.InvalidHandshake` to abort the + connection. + + :rfc:`6455` leaves the rules up to the specification of each + :extension. + + To provide this level of flexibility, for each extension accepted by + the server, we check for a match with each extension available in the + client configuration. If no match is found, an exception is raised. + + If several variants of the same extension are accepted by the server, + it may be configured severel times, which won't make sense in general. + Extensions must implement their own requirements. For this purpose, + the list of previously accepted extensions is provided. + + Other requirements, for example related to mandatory extensions or the + order of extensions, may be implemented by overriding this method. + + """ + accepted_extensions: List[Extension] = [] + + header_values = headers.get_all("Sec-WebSocket-Extensions") + + if header_values: + + if available_extensions is None: + raise InvalidHandshake("no extensions supported") + + parsed_header_values: List[ExtensionHeader] = sum( + [parse_extension(header_value) for header_value in header_values], [] + ) + + for name, response_params in parsed_header_values: + + for extension_factory in available_extensions: + + # Skip non-matching extensions based on their name. + if extension_factory.name != name: + continue + + # Skip non-matching extensions based on their params. + try: + extension = extension_factory.process_response_params( + response_params, accepted_extensions + ) + except NegotiationError: + continue + + # Add matching extension to the final list. + accepted_extensions.append(extension) + + # Break out of the loop once we have a match. + break + + # If we didn't break from the loop, no extension in our list + # matched what the server sent. Fail the connection. + else: + raise NegotiationError( + f"Unsupported extension: " + f"name = {name}, params = {response_params}" + ) + + return accepted_extensions + + @staticmethod + def process_subprotocol( + headers: Headers, available_subprotocols: Optional[Sequence[Subprotocol]] + ) -> Optional[Subprotocol]: + """ + Handle the Sec-WebSocket-Protocol HTTP response header. + + Check that it contains exactly one supported subprotocol. + + Return the selected subprotocol. + + """ + subprotocol: Optional[Subprotocol] = None + + header_values = headers.get_all("Sec-WebSocket-Protocol") + + if header_values: + + if available_subprotocols is None: + raise InvalidHandshake("no subprotocols supported") + + parsed_header_values: Sequence[Subprotocol] = sum( + [parse_subprotocol(header_value) for header_value in header_values], [] + ) + + if len(parsed_header_values) > 1: + subprotocols = ", ".join(parsed_header_values) + raise InvalidHandshake(f"multiple subprotocols: {subprotocols}") + + subprotocol = parsed_header_values[0] + + if subprotocol not in available_subprotocols: + raise NegotiationError(f"unsupported subprotocol: {subprotocol}") + + return subprotocol + + async def handshake( + self, + wsuri: WebSocketURI, + origin: Optional[Origin] = None, + available_extensions: Optional[Sequence[ClientExtensionFactory]] = None, + available_subprotocols: Optional[Sequence[Subprotocol]] = None, + extra_headers: Optional[HeadersLike] = None, + ) -> None: + """ + Perform the client side of the opening handshake. + + :param origin: sets the Origin HTTP header + :param available_extensions: list of supported extensions in the order + in which they should be used + :param available_subprotocols: list of supported subprotocols in order + of decreasing preference + :param extra_headers: sets additional HTTP request headers; it must be + a :class:`~websockets.http.Headers` instance, a + :class:`~collections.abc.Mapping`, or an iterable of ``(name, + value)`` pairs + :raises ~websockets.exceptions.InvalidHandshake: if the handshake + fails + + """ + request_headers = Headers() + + if wsuri.port == (443 if wsuri.secure else 80): # pragma: no cover + request_headers["Host"] = wsuri.host + else: + request_headers["Host"] = f"{wsuri.host}:{wsuri.port}" + + if wsuri.user_info: + request_headers["Authorization"] = build_authorization_basic( + *wsuri.user_info + ) + + if origin is not None: + request_headers["Origin"] = origin + + key = build_request(request_headers) + + if available_extensions is not None: + extensions_header = build_extension( + [ + (extension_factory.name, extension_factory.get_request_params()) + for extension_factory in available_extensions + ] + ) + request_headers["Sec-WebSocket-Extensions"] = extensions_header + + if available_subprotocols is not None: + protocol_header = build_subprotocol(available_subprotocols) + request_headers["Sec-WebSocket-Protocol"] = protocol_header + + if extra_headers is not None: + if isinstance(extra_headers, Headers): + extra_headers = extra_headers.raw_items() + elif isinstance(extra_headers, collections.abc.Mapping): + extra_headers = extra_headers.items() + for name, value in extra_headers: + request_headers[name] = value + + request_headers.setdefault("User-Agent", USER_AGENT) + + self.write_http_request(wsuri.resource_name, request_headers) + + status_code, response_headers = await self.read_http_response() + if status_code in (301, 302, 303, 307, 308): + if "Location" not in response_headers: + raise InvalidHeader("Location") + raise RedirectHandshake(response_headers["Location"]) + elif status_code != 101: + raise InvalidStatusCode(status_code) + + check_response(response_headers, key) + + self.extensions = self.process_extensions( + response_headers, available_extensions + ) + + self.subprotocol = self.process_subprotocol( + response_headers, available_subprotocols + ) + + self.connection_open() + + +class Connect: + """ + Connect to the WebSocket server at the given ``uri``. + + Awaiting :func:`connect` yields a :class:`WebSocketClientProtocol` which + can then be used to send and receive messages. + + :func:`connect` can also be used as a asynchronous context manager. In + that case, the connection is closed when exiting the context. + + :func:`connect` is a wrapper around the event loop's + :meth:`~asyncio.loop.create_connection` method. Unknown keyword arguments + are passed to :meth:`~asyncio.loop.create_connection`. + + For example, you can set the ``ssl`` keyword argument to a + :class:`~ssl.SSLContext` to enforce some TLS settings. When connecting to + a ``wss://`` URI, if this argument isn't provided explicitly, + :func:`ssl.create_default_context` is called to create a context. + + You can connect to a different host and port from those found in ``uri`` + by setting ``host`` and ``port`` keyword arguments. This only changes the + destination of the TCP connection. The host name from ``uri`` is still + used in the TLS handshake for secure connections and in the ``Host`` HTTP + header. + + The ``create_protocol`` parameter allows customizing the + :class:`~asyncio.Protocol` that manages the connection. It should be a + callable or class accepting the same arguments as + :class:`WebSocketClientProtocol` and returning an instance of + :class:`WebSocketClientProtocol` or a subclass. It defaults to + :class:`WebSocketClientProtocol`. + + The behavior of ``ping_interval``, ``ping_timeout``, ``close_timeout``, + ``max_size``, ``max_queue``, ``read_limit``, and ``write_limit`` is + described in :class:`~websockets.protocol.WebSocketCommonProtocol`. + + :func:`connect` also accepts the following optional arguments: + + * ``compression`` is a shortcut to configure compression extensions; + by default it enables the "permessage-deflate" extension; set it to + ``None`` to disable compression + * ``origin`` sets the Origin HTTP header + * ``extensions`` is a list of supported extensions in order of + decreasing preference + * ``subprotocols`` is a list of supported subprotocols in order of + decreasing preference + * ``extra_headers`` sets additional HTTP request headers; it can be a + :class:`~websockets.http.Headers` instance, a + :class:`~collections.abc.Mapping`, or an iterable of ``(name, value)`` + pairs + + :raises ~websockets.uri.InvalidURI: if ``uri`` is invalid + :raises ~websockets.handshake.InvalidHandshake: if the opening handshake + fails + + """ + + MAX_REDIRECTS_ALLOWED = 10 + + def __init__( + self, + uri: str, + *, + path: Optional[str] = None, + create_protocol: Optional[Type[WebSocketClientProtocol]] = None, + ping_interval: float = 20, + ping_timeout: float = 20, + close_timeout: Optional[float] = None, + max_size: int = 2 ** 20, + max_queue: int = 2 ** 5, + read_limit: int = 2 ** 16, + write_limit: int = 2 ** 16, + loop: Optional[asyncio.AbstractEventLoop] = None, + legacy_recv: bool = False, + klass: Optional[Type[WebSocketClientProtocol]] = None, + timeout: Optional[float] = None, + compression: Optional[str] = "deflate", + origin: Optional[Origin] = None, + extensions: Optional[Sequence[ClientExtensionFactory]] = None, + subprotocols: Optional[Sequence[Subprotocol]] = None, + extra_headers: Optional[HeadersLike] = None, + **kwargs: Any, + ) -> None: + # Backwards compatibility: close_timeout used to be called timeout. + if timeout is None: + timeout = 10 + else: + warnings.warn("rename timeout to close_timeout", DeprecationWarning) + # If both are specified, timeout is ignored. + if close_timeout is None: + close_timeout = timeout + + # Backwards compatibility: create_protocol used to be called klass. + if klass is None: + klass = WebSocketClientProtocol + else: + warnings.warn("rename klass to create_protocol", DeprecationWarning) + # If both are specified, klass is ignored. + if create_protocol is None: + create_protocol = klass + + if loop is None: + loop = asyncio.get_event_loop() + + wsuri = parse_uri(uri) + if wsuri.secure: + kwargs.setdefault("ssl", True) + elif kwargs.get("ssl") is not None: + raise ValueError( + "connect() received a ssl argument for a ws:// URI, " + "use a wss:// URI to enable TLS" + ) + + if compression == "deflate": + if extensions is None: + extensions = [] + if not any( + extension_factory.name == ClientPerMessageDeflateFactory.name + for extension_factory in extensions + ): + extensions = list(extensions) + [ + ClientPerMessageDeflateFactory(client_max_window_bits=True) + ] + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + factory = functools.partial( + create_protocol, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_size=max_size, + max_queue=max_queue, + read_limit=read_limit, + write_limit=write_limit, + loop=loop, + host=wsuri.host, + port=wsuri.port, + secure=wsuri.secure, + legacy_recv=legacy_recv, + origin=origin, + extensions=extensions, + subprotocols=subprotocols, + extra_headers=extra_headers, + ) + + if path is None: + host: Optional[str] + port: Optional[int] + if kwargs.get("sock") is None: + host, port = wsuri.host, wsuri.port + else: + # If sock is given, host and port shouldn't be specified. + host, port = None, None + # If host and port are given, override values from the URI. + host = kwargs.pop("host", host) + port = kwargs.pop("port", port) + create_connection = functools.partial( + loop.create_connection, factory, host, port, **kwargs + ) + else: + create_connection = functools.partial( + loop.create_unix_connection, factory, path, **kwargs + ) + + # This is a coroutine function. + self._create_connection = create_connection + self._wsuri = wsuri + + def handle_redirect(self, uri: str) -> None: + # Update the state of this instance to connect to a new URI. + old_wsuri = self._wsuri + new_wsuri = parse_uri(uri) + + # Forbid TLS downgrade. + if old_wsuri.secure and not new_wsuri.secure: + raise SecurityError("redirect from WSS to WS") + + same_origin = ( + old_wsuri.host == new_wsuri.host and old_wsuri.port == new_wsuri.port + ) + + # Rewrite the host and port arguments for cross-origin redirects. + # This preserves connection overrides with the host and port + # arguments if the redirect points to the same host and port. + if not same_origin: + # Replace the host and port argument passed to the protocol factory. + factory = self._create_connection.args[0] + factory = functools.partial( + factory.func, + *factory.args, + **dict(factory.keywords, host=new_wsuri.host, port=new_wsuri.port), + ) + # Replace the host and port argument passed to create_connection. + self._create_connection = functools.partial( + self._create_connection.func, + *(factory, new_wsuri.host, new_wsuri.port), + **self._create_connection.keywords, + ) + + # Set the new WebSocket URI. This suffices for same-origin redirects. + self._wsuri = new_wsuri + + # async with connect(...) + + async def __aenter__(self) -> WebSocketClientProtocol: + return await self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + await self.ws_client.close() + + # await connect(...) + + def __await__(self) -> Generator[Any, None, WebSocketClientProtocol]: + # Create a suitable iterator by calling __await__ on a coroutine. + return self.__await_impl__().__await__() + + async def __await_impl__(self) -> WebSocketClientProtocol: + for redirects in range(self.MAX_REDIRECTS_ALLOWED): + transport, protocol = await self._create_connection() + # https://github.com/python/typeshed/pull/2756 + transport = cast(asyncio.Transport, transport) + protocol = cast(WebSocketClientProtocol, protocol) + + try: + try: + await protocol.handshake( + self._wsuri, + origin=protocol.origin, + available_extensions=protocol.available_extensions, + available_subprotocols=protocol.available_subprotocols, + extra_headers=protocol.extra_headers, + ) + except Exception: + protocol.fail_connection() + await protocol.wait_closed() + raise + else: + self.ws_client = protocol + return protocol + except RedirectHandshake as exc: + self.handle_redirect(exc.uri) + else: + raise SecurityError("too many redirects") + + # yield from connect(...) + + __iter__ = __await__ + + +connect = Connect + + +def unix_connect(path: str, uri: str = "ws://localhost/", **kwargs: Any) -> Connect: + """ + Similar to :func:`connect`, but for connecting to a Unix socket. + + This function calls the event loop's + :meth:`~asyncio.loop.create_unix_connection` method. + + It is only available on Unix. + + It's mainly useful for debugging servers listening on Unix sockets. + + :param path: file system path to the Unix socket + :param uri: WebSocket URI + + """ + return connect(uri=uri, path=path, **kwargs) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py new file mode 100644 index 00000000000..9873a171703 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/exceptions.py @@ -0,0 +1,366 @@ +""" +:mod:`websockets.exceptions` defines the following exception hierarchy: + +* :exc:`WebSocketException` + * :exc:`ConnectionClosed` + * :exc:`ConnectionClosedError` + * :exc:`ConnectionClosedOK` + * :exc:`InvalidHandshake` + * :exc:`SecurityError` + * :exc:`InvalidMessage` + * :exc:`InvalidHeader` + * :exc:`InvalidHeaderFormat` + * :exc:`InvalidHeaderValue` + * :exc:`InvalidOrigin` + * :exc:`InvalidUpgrade` + * :exc:`InvalidStatusCode` + * :exc:`NegotiationError` + * :exc:`DuplicateParameter` + * :exc:`InvalidParameterName` + * :exc:`InvalidParameterValue` + * :exc:`AbortHandshake` + * :exc:`RedirectHandshake` + * :exc:`InvalidState` + * :exc:`InvalidURI` + * :exc:`PayloadTooBig` + * :exc:`ProtocolError` + +""" + +import http +from typing import Optional + +from .http import Headers, HeadersLike + + +__all__ = [ + "WebSocketException", + "ConnectionClosed", + "ConnectionClosedError", + "ConnectionClosedOK", + "InvalidHandshake", + "SecurityError", + "InvalidMessage", + "InvalidHeader", + "InvalidHeaderFormat", + "InvalidHeaderValue", + "InvalidOrigin", + "InvalidUpgrade", + "InvalidStatusCode", + "NegotiationError", + "DuplicateParameter", + "InvalidParameterName", + "InvalidParameterValue", + "AbortHandshake", + "RedirectHandshake", + "InvalidState", + "InvalidURI", + "PayloadTooBig", + "ProtocolError", + "WebSocketProtocolError", +] + + +class WebSocketException(Exception): + """ + Base class for all exceptions defined by :mod:`websockets`. + + """ + + +CLOSE_CODES = { + 1000: "OK", + 1001: "going away", + 1002: "protocol error", + 1003: "unsupported type", + # 1004 is reserved + 1005: "no status code [internal]", + 1006: "connection closed abnormally [internal]", + 1007: "invalid data", + 1008: "policy violation", + 1009: "message too big", + 1010: "extension required", + 1011: "unexpected error", + 1015: "TLS failure [internal]", +} + + +def format_close(code: int, reason: str) -> str: + """ + Display a human-readable version of the close code and reason. + + """ + if 3000 <= code < 4000: + explanation = "registered" + elif 4000 <= code < 5000: + explanation = "private use" + else: + explanation = CLOSE_CODES.get(code, "unknown") + result = f"code = {code} ({explanation}), " + + if reason: + result += f"reason = {reason}" + else: + result += "no reason" + + return result + + +class ConnectionClosed(WebSocketException): + """ + Raised when trying to interact with a closed connection. + + Provides the connection close code and reason in its ``code`` and + ``reason`` attributes respectively. + + """ + + def __init__(self, code: int, reason: str) -> None: + self.code = code + self.reason = reason + super().__init__(format_close(code, reason)) + + +class ConnectionClosedError(ConnectionClosed): + """ + Like :exc:`ConnectionClosed`, when the connection terminated with an error. + + This means the close code is different from 1000 (OK) and 1001 (going away). + + """ + + def __init__(self, code: int, reason: str) -> None: + assert code != 1000 and code != 1001 + super().__init__(code, reason) + + +class ConnectionClosedOK(ConnectionClosed): + """ + Like :exc:`ConnectionClosed`, when the connection terminated properly. + + This means the close code is 1000 (OK) or 1001 (going away). + + """ + + def __init__(self, code: int, reason: str) -> None: + assert code == 1000 or code == 1001 + super().__init__(code, reason) + + +class InvalidHandshake(WebSocketException): + """ + Raised during the handshake when the WebSocket connection fails. + + """ + + +class SecurityError(InvalidHandshake): + """ + Raised when a handshake request or response breaks a security rule. + + Security limits are hard coded. + + """ + + +class InvalidMessage(InvalidHandshake): + """ + Raised when a handshake request or response is malformed. + + """ + + +class InvalidHeader(InvalidHandshake): + """ + Raised when a HTTP header doesn't have a valid format or value. + + """ + + def __init__(self, name: str, value: Optional[str] = None) -> None: + self.name = name + self.value = value + if value is None: + message = f"missing {name} header" + elif value == "": + message = f"empty {name} header" + else: + message = f"invalid {name} header: {value}" + super().__init__(message) + + +class InvalidHeaderFormat(InvalidHeader): + """ + Raised when a HTTP header cannot be parsed. + + The format of the header doesn't match the grammar for that header. + + """ + + def __init__(self, name: str, error: str, header: str, pos: int) -> None: + self.name = name + error = f"{error} at {pos} in {header}" + super().__init__(name, error) + + +class InvalidHeaderValue(InvalidHeader): + """ + Raised when a HTTP header has a wrong value. + + The format of the header is correct but a value isn't acceptable. + + """ + + +class InvalidOrigin(InvalidHeader): + """ + Raised when the Origin header in a request isn't allowed. + + """ + + def __init__(self, origin: Optional[str]) -> None: + super().__init__("Origin", origin) + + +class InvalidUpgrade(InvalidHeader): + """ + Raised when the Upgrade or Connection header isn't correct. + + """ + + +class InvalidStatusCode(InvalidHandshake): + """ + Raised when a handshake response status code is invalid. + + The integer status code is available in the ``status_code`` attribute. + + """ + + def __init__(self, status_code: int) -> None: + self.status_code = status_code + message = f"server rejected WebSocket connection: HTTP {status_code}" + super().__init__(message) + + +class NegotiationError(InvalidHandshake): + """ + Raised when negotiating an extension fails. + + """ + + +class DuplicateParameter(NegotiationError): + """ + Raised when a parameter name is repeated in an extension header. + + """ + + def __init__(self, name: str) -> None: + self.name = name + message = f"duplicate parameter: {name}" + super().__init__(message) + + +class InvalidParameterName(NegotiationError): + """ + Raised when a parameter name in an extension header is invalid. + + """ + + def __init__(self, name: str) -> None: + self.name = name + message = f"invalid parameter name: {name}" + super().__init__(message) + + +class InvalidParameterValue(NegotiationError): + """ + Raised when a parameter value in an extension header is invalid. + + """ + + def __init__(self, name: str, value: Optional[str]) -> None: + self.name = name + self.value = value + if value is None: + message = f"missing value for parameter {name}" + elif value == "": + message = f"empty value for parameter {name}" + else: + message = f"invalid value for parameter {name}: {value}" + super().__init__(message) + + +class AbortHandshake(InvalidHandshake): + """ + Raised to abort the handshake on purpose and return a HTTP response. + + This exception is an implementation detail. + + The public API is :meth:`~server.WebSocketServerProtocol.process_request`. + + """ + + def __init__( + self, status: http.HTTPStatus, headers: HeadersLike, body: bytes = b"" + ) -> None: + self.status = status + self.headers = Headers(headers) + self.body = body + message = f"HTTP {status}, {len(self.headers)} headers, {len(body)} bytes" + super().__init__(message) + + +class RedirectHandshake(InvalidHandshake): + """ + Raised when a handshake gets redirected. + + This exception is an implementation detail. + + """ + + def __init__(self, uri: str) -> None: + self.uri = uri + + def __str__(self) -> str: + return f"redirect to {self.uri}" + + +class InvalidState(WebSocketException, AssertionError): + """ + Raised when an operation is forbidden in the current state. + + This exception is an implementation detail. + + It should never be raised in normal circumstances. + + """ + + +class InvalidURI(WebSocketException): + """ + Raised when connecting to an URI that isn't a valid WebSocket URI. + + """ + + def __init__(self, uri: str) -> None: + self.uri = uri + message = "{} isn't a valid URI".format(uri) + super().__init__(message) + + +class PayloadTooBig(WebSocketException): + """ + Raised when receiving a frame with a payload exceeding the maximum size. + + """ + + +class ProtocolError(WebSocketException): + """ + Raised when the other side breaks the protocol. + + """ + + +WebSocketProtocolError = ProtocolError # for backwards compatibility diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/__init__.py diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py new file mode 100644 index 00000000000..aa52a7adbf4 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/base.py @@ -0,0 +1,119 @@ +""" +:mod:`websockets.extensions.base` defines abstract classes for implementing +extensions. + +See `section 9 of RFC 6455`_. + +.. _section 9 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-9 + +""" + +from typing import List, Optional, Sequence, Tuple + +from ..framing import Frame +from ..typing import ExtensionName, ExtensionParameter + + +__all__ = ["Extension", "ClientExtensionFactory", "ServerExtensionFactory"] + + +class Extension: + """ + Abstract class for extensions. + + """ + + @property + def name(self) -> ExtensionName: + """ + Extension identifier. + + """ + + def decode(self, frame: Frame, *, max_size: Optional[int] = None) -> Frame: + """ + Decode an incoming frame. + + :param frame: incoming frame + :param max_size: maximum payload size in bytes + + """ + + def encode(self, frame: Frame) -> Frame: + """ + Encode an outgoing frame. + + :param frame: outgoing frame + + """ + + +class ClientExtensionFactory: + """ + Abstract class for client-side extension factories. + + """ + + @property + def name(self) -> ExtensionName: + """ + Extension identifier. + + """ + + def get_request_params(self) -> List[ExtensionParameter]: + """ + Build request parameters. + + Return a list of ``(name, value)`` pairs. + + """ + + def process_response_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence[Extension], + ) -> Extension: + """ + Process response parameters received from the server. + + :param params: list of ``(name, value)`` pairs. + :param accepted_extensions: list of previously accepted extensions. + :raises ~websockets.exceptions.NegotiationError: if parameters aren't + acceptable + + """ + + +class ServerExtensionFactory: + """ + Abstract class for server-side extension factories. + + """ + + @property + def name(self) -> ExtensionName: + """ + Extension identifier. + + """ + + def process_request_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence[Extension], + ) -> Tuple[List[ExtensionParameter], Extension]: + """ + Process request parameters received from the client. + + To accept the offer, return a 2-uple containing: + + - response parameters: a list of ``(name, value)`` pairs + - an extension: an instance of a subclass of :class:`Extension` + + :param params: list of ``(name, value)`` pairs. + :param accepted_extensions: list of previously accepted extensions. + :raises ~websockets.exceptions.NegotiationError: to reject the offer, + if parameters aren't acceptable + + """ diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py new file mode 100644 index 00000000000..e38d9edaba6 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py @@ -0,0 +1,588 @@ +""" +:mod:`websockets.extensions.permessage_deflate` implements the Compression +Extensions for WebSocket as specified in :rfc:`7692`. + +""" + +import zlib +from typing import Any, Dict, List, Optional, Sequence, Tuple, Union + +from ..exceptions import ( + DuplicateParameter, + InvalidParameterName, + InvalidParameterValue, + NegotiationError, + PayloadTooBig, +) +from ..framing import CTRL_OPCODES, OP_CONT, Frame +from ..typing import ExtensionName, ExtensionParameter +from .base import ClientExtensionFactory, Extension, ServerExtensionFactory + + +__all__ = [ + "PerMessageDeflate", + "ClientPerMessageDeflateFactory", + "ServerPerMessageDeflateFactory", +] + +_EMPTY_UNCOMPRESSED_BLOCK = b"\x00\x00\xff\xff" + +_MAX_WINDOW_BITS_VALUES = [str(bits) for bits in range(8, 16)] + + +class PerMessageDeflate(Extension): + """ + Per-Message Deflate extension. + + """ + + name = ExtensionName("permessage-deflate") + + def __init__( + self, + remote_no_context_takeover: bool, + local_no_context_takeover: bool, + remote_max_window_bits: int, + local_max_window_bits: int, + compress_settings: Optional[Dict[Any, Any]] = None, + ) -> None: + """ + Configure the Per-Message Deflate extension. + + """ + if compress_settings is None: + compress_settings = {} + + assert remote_no_context_takeover in [False, True] + assert local_no_context_takeover in [False, True] + assert 8 <= remote_max_window_bits <= 15 + assert 8 <= local_max_window_bits <= 15 + assert "wbits" not in compress_settings + + self.remote_no_context_takeover = remote_no_context_takeover + self.local_no_context_takeover = local_no_context_takeover + self.remote_max_window_bits = remote_max_window_bits + self.local_max_window_bits = local_max_window_bits + self.compress_settings = compress_settings + + if not self.remote_no_context_takeover: + self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits) + + if not self.local_no_context_takeover: + self.encoder = zlib.compressobj( + wbits=-self.local_max_window_bits, **self.compress_settings + ) + + # To handle continuation frames properly, we must keep track of + # whether that initial frame was encoded. + self.decode_cont_data = False + # There's no need for self.encode_cont_data because we always encode + # outgoing frames, so it would always be True. + + def __repr__(self) -> str: + return ( + f"PerMessageDeflate(" + f"remote_no_context_takeover={self.remote_no_context_takeover}, " + f"local_no_context_takeover={self.local_no_context_takeover}, " + f"remote_max_window_bits={self.remote_max_window_bits}, " + f"local_max_window_bits={self.local_max_window_bits})" + ) + + def decode(self, frame: Frame, *, max_size: Optional[int] = None) -> Frame: + """ + Decode an incoming frame. + + """ + # Skip control frames. + if frame.opcode in CTRL_OPCODES: + return frame + + # Handle continuation data frames: + # - skip if the initial data frame wasn't encoded + # - reset "decode continuation data" flag if it's a final frame + if frame.opcode == OP_CONT: + if not self.decode_cont_data: + return frame + if frame.fin: + self.decode_cont_data = False + + # Handle text and binary data frames: + # - skip if the frame isn't encoded + # - set "decode continuation data" flag if it's a non-final frame + else: + if not frame.rsv1: + return frame + if not frame.fin: # frame.rsv1 is True at this point + self.decode_cont_data = True + + # Re-initialize per-message decoder. + if self.remote_no_context_takeover: + self.decoder = zlib.decompressobj(wbits=-self.remote_max_window_bits) + + # Uncompress compressed frames. Protect against zip bombs by + # preventing zlib from decompressing more than max_length bytes + # (except when the limit is disabled with max_size = None). + data = frame.data + if frame.fin: + data += _EMPTY_UNCOMPRESSED_BLOCK + max_length = 0 if max_size is None else max_size + data = self.decoder.decompress(data, max_length) + if self.decoder.unconsumed_tail: + raise PayloadTooBig( + f"Uncompressed payload length exceeds size limit (? > {max_size} bytes)" + ) + + # Allow garbage collection of the decoder if it won't be reused. + if frame.fin and self.remote_no_context_takeover: + del self.decoder + + return frame._replace(data=data, rsv1=False) + + def encode(self, frame: Frame) -> Frame: + """ + Encode an outgoing frame. + + """ + # Skip control frames. + if frame.opcode in CTRL_OPCODES: + return frame + + # Since we always encode and never fragment messages, there's no logic + # similar to decode() here at this time. + + if frame.opcode != OP_CONT: + # Re-initialize per-message decoder. + if self.local_no_context_takeover: + self.encoder = zlib.compressobj( + wbits=-self.local_max_window_bits, **self.compress_settings + ) + + # Compress data frames. + data = self.encoder.compress(frame.data) + self.encoder.flush(zlib.Z_SYNC_FLUSH) + if frame.fin and data.endswith(_EMPTY_UNCOMPRESSED_BLOCK): + data = data[:-4] + + # Allow garbage collection of the encoder if it won't be reused. + if frame.fin and self.local_no_context_takeover: + del self.encoder + + return frame._replace(data=data, rsv1=True) + + +def _build_parameters( + server_no_context_takeover: bool, + client_no_context_takeover: bool, + server_max_window_bits: Optional[int], + client_max_window_bits: Optional[Union[int, bool]], +) -> List[ExtensionParameter]: + """ + Build a list of ``(name, value)`` pairs for some compression parameters. + + """ + params: List[ExtensionParameter] = [] + if server_no_context_takeover: + params.append(("server_no_context_takeover", None)) + if client_no_context_takeover: + params.append(("client_no_context_takeover", None)) + if server_max_window_bits: + params.append(("server_max_window_bits", str(server_max_window_bits))) + if client_max_window_bits is True: # only in handshake requests + params.append(("client_max_window_bits", None)) + elif client_max_window_bits: + params.append(("client_max_window_bits", str(client_max_window_bits))) + return params + + +def _extract_parameters( + params: Sequence[ExtensionParameter], *, is_server: bool +) -> Tuple[bool, bool, Optional[int], Optional[Union[int, bool]]]: + """ + Extract compression parameters from a list of ``(name, value)`` pairs. + + If ``is_server`` is ``True``, ``client_max_window_bits`` may be provided + without a value. This is only allow in handshake requests. + + """ + server_no_context_takeover: bool = False + client_no_context_takeover: bool = False + server_max_window_bits: Optional[int] = None + client_max_window_bits: Optional[Union[int, bool]] = None + + for name, value in params: + + if name == "server_no_context_takeover": + if server_no_context_takeover: + raise DuplicateParameter(name) + if value is None: + server_no_context_takeover = True + else: + raise InvalidParameterValue(name, value) + + elif name == "client_no_context_takeover": + if client_no_context_takeover: + raise DuplicateParameter(name) + if value is None: + client_no_context_takeover = True + else: + raise InvalidParameterValue(name, value) + + elif name == "server_max_window_bits": + if server_max_window_bits is not None: + raise DuplicateParameter(name) + if value in _MAX_WINDOW_BITS_VALUES: + server_max_window_bits = int(value) + else: + raise InvalidParameterValue(name, value) + + elif name == "client_max_window_bits": + if client_max_window_bits is not None: + raise DuplicateParameter(name) + if is_server and value is None: # only in handshake requests + client_max_window_bits = True + elif value in _MAX_WINDOW_BITS_VALUES: + client_max_window_bits = int(value) + else: + raise InvalidParameterValue(name, value) + + else: + raise InvalidParameterName(name) + + return ( + server_no_context_takeover, + client_no_context_takeover, + server_max_window_bits, + client_max_window_bits, + ) + + +class ClientPerMessageDeflateFactory(ClientExtensionFactory): + """ + Client-side extension factory for the Per-Message Deflate extension. + + Parameters behave as described in `section 7.1 of RFC 7692`_. Set them to + ``True`` to include them in the negotiation offer without a value or to an + integer value to include them with this value. + + .. _section 7.1 of RFC 7692: https://tools.ietf.org/html/rfc7692#section-7.1 + + :param server_no_context_takeover: defaults to ``False`` + :param client_no_context_takeover: defaults to ``False`` + :param server_max_window_bits: optional, defaults to ``None`` + :param client_max_window_bits: optional, defaults to ``None`` + :param compress_settings: optional, keyword arguments for + :func:`zlib.compressobj`, excluding ``wbits`` + + """ + + name = ExtensionName("permessage-deflate") + + def __init__( + self, + server_no_context_takeover: bool = False, + client_no_context_takeover: bool = False, + server_max_window_bits: Optional[int] = None, + client_max_window_bits: Optional[Union[int, bool]] = None, + compress_settings: Optional[Dict[str, Any]] = None, + ) -> None: + """ + Configure the Per-Message Deflate extension factory. + + """ + if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15): + raise ValueError("server_max_window_bits must be between 8 and 15") + if not ( + client_max_window_bits is None + or client_max_window_bits is True + or 8 <= client_max_window_bits <= 15 + ): + raise ValueError("client_max_window_bits must be between 8 and 15") + if compress_settings is not None and "wbits" in compress_settings: + raise ValueError( + "compress_settings must not include wbits, " + "set client_max_window_bits instead" + ) + + self.server_no_context_takeover = server_no_context_takeover + self.client_no_context_takeover = client_no_context_takeover + self.server_max_window_bits = server_max_window_bits + self.client_max_window_bits = client_max_window_bits + self.compress_settings = compress_settings + + def get_request_params(self) -> List[ExtensionParameter]: + """ + Build request parameters. + + """ + return _build_parameters( + self.server_no_context_takeover, + self.client_no_context_takeover, + self.server_max_window_bits, + self.client_max_window_bits, + ) + + def process_response_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence["Extension"], + ) -> PerMessageDeflate: + """ + Process response parameters. + + Return an extension instance. + + """ + if any(other.name == self.name for other in accepted_extensions): + raise NegotiationError(f"received duplicate {self.name}") + + # Request parameters are available in instance variables. + + # Load response parameters in local variables. + ( + server_no_context_takeover, + client_no_context_takeover, + server_max_window_bits, + client_max_window_bits, + ) = _extract_parameters(params, is_server=False) + + # After comparing the request and the response, the final + # configuration must be available in the local variables. + + # server_no_context_takeover + # + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # False False False + # False True True + # True False Error! + # True True True + + if self.server_no_context_takeover: + if not server_no_context_takeover: + raise NegotiationError("expected server_no_context_takeover") + + # client_no_context_takeover + # + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # False False False + # False True True + # True False True - must change value + # True True True + + if self.client_no_context_takeover: + if not client_no_context_takeover: + client_no_context_takeover = True + + # server_max_window_bits + + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # None None None + # None 8≤M≤15 M + # 8≤N≤15 None Error! + # 8≤N≤15 8≤M≤N M + # 8≤N≤15 N<M≤15 Error! + + if self.server_max_window_bits is None: + pass + + else: + if server_max_window_bits is None: + raise NegotiationError("expected server_max_window_bits") + elif server_max_window_bits > self.server_max_window_bits: + raise NegotiationError("unsupported server_max_window_bits") + + # client_max_window_bits + + # Req. Resp. Result + # ------ ------ -------------------------------------------------- + # None None None + # None 8≤M≤15 Error! + # True None None + # True 8≤M≤15 M + # 8≤N≤15 None N - must change value + # 8≤N≤15 8≤M≤N M + # 8≤N≤15 N<M≤15 Error! + + if self.client_max_window_bits is None: + if client_max_window_bits is not None: + raise NegotiationError("unexpected client_max_window_bits") + + elif self.client_max_window_bits is True: + pass + + else: + if client_max_window_bits is None: + client_max_window_bits = self.client_max_window_bits + elif client_max_window_bits > self.client_max_window_bits: + raise NegotiationError("unsupported client_max_window_bits") + + return PerMessageDeflate( + server_no_context_takeover, # remote_no_context_takeover + client_no_context_takeover, # local_no_context_takeover + server_max_window_bits or 15, # remote_max_window_bits + client_max_window_bits or 15, # local_max_window_bits + self.compress_settings, + ) + + +class ServerPerMessageDeflateFactory(ServerExtensionFactory): + """ + Server-side extension factory for the Per-Message Deflate extension. + + Parameters behave as described in `section 7.1 of RFC 7692`_. Set them to + ``True`` to include them in the negotiation offer without a value or to an + integer value to include them with this value. + + .. _section 7.1 of RFC 7692: https://tools.ietf.org/html/rfc7692#section-7.1 + + :param server_no_context_takeover: defaults to ``False`` + :param client_no_context_takeover: defaults to ``False`` + :param server_max_window_bits: optional, defaults to ``None`` + :param client_max_window_bits: optional, defaults to ``None`` + :param compress_settings: optional, keyword arguments for + :func:`zlib.compressobj`, excluding ``wbits`` + + """ + + name = ExtensionName("permessage-deflate") + + def __init__( + self, + server_no_context_takeover: bool = False, + client_no_context_takeover: bool = False, + server_max_window_bits: Optional[int] = None, + client_max_window_bits: Optional[int] = None, + compress_settings: Optional[Dict[str, Any]] = None, + ) -> None: + """ + Configure the Per-Message Deflate extension factory. + + """ + if not (server_max_window_bits is None or 8 <= server_max_window_bits <= 15): + raise ValueError("server_max_window_bits must be between 8 and 15") + if not (client_max_window_bits is None or 8 <= client_max_window_bits <= 15): + raise ValueError("client_max_window_bits must be between 8 and 15") + if compress_settings is not None and "wbits" in compress_settings: + raise ValueError( + "compress_settings must not include wbits, " + "set server_max_window_bits instead" + ) + + self.server_no_context_takeover = server_no_context_takeover + self.client_no_context_takeover = client_no_context_takeover + self.server_max_window_bits = server_max_window_bits + self.client_max_window_bits = client_max_window_bits + self.compress_settings = compress_settings + + def process_request_params( + self, + params: Sequence[ExtensionParameter], + accepted_extensions: Sequence["Extension"], + ) -> Tuple[List[ExtensionParameter], PerMessageDeflate]: + """ + Process request parameters. + + Return response params and an extension instance. + + """ + if any(other.name == self.name for other in accepted_extensions): + raise NegotiationError(f"skipped duplicate {self.name}") + + # Load request parameters in local variables. + ( + server_no_context_takeover, + client_no_context_takeover, + server_max_window_bits, + client_max_window_bits, + ) = _extract_parameters(params, is_server=True) + + # Configuration parameters are available in instance variables. + + # After comparing the request and the configuration, the response must + # be available in the local variables. + + # server_no_context_takeover + # + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # False False False + # False True True + # True False True - must change value to True + # True True True + + if self.server_no_context_takeover: + if not server_no_context_takeover: + server_no_context_takeover = True + + # client_no_context_takeover + # + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # False False False + # False True True (or False) + # True False True - must change value to True + # True True True (or False) + + if self.client_no_context_takeover: + if not client_no_context_takeover: + client_no_context_takeover = True + + # server_max_window_bits + + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # None None None + # None 8≤M≤15 M + # 8≤N≤15 None N - must change value + # 8≤N≤15 8≤M≤N M + # 8≤N≤15 N<M≤15 N - must change value + + if self.server_max_window_bits is None: + pass + + else: + if server_max_window_bits is None: + server_max_window_bits = self.server_max_window_bits + elif server_max_window_bits > self.server_max_window_bits: + server_max_window_bits = self.server_max_window_bits + + # client_max_window_bits + + # Config Req. Resp. + # ------ ------ -------------------------------------------------- + # None None None + # None True None - must change value + # None 8≤M≤15 M (or None) + # 8≤N≤15 None Error! + # 8≤N≤15 True N - must change value + # 8≤N≤15 8≤M≤N M (or None) + # 8≤N≤15 N<M≤15 N + + if self.client_max_window_bits is None: + if client_max_window_bits is True: + client_max_window_bits = self.client_max_window_bits + + else: + if client_max_window_bits is None: + raise NegotiationError("required client_max_window_bits") + elif client_max_window_bits is True: + client_max_window_bits = self.client_max_window_bits + elif self.client_max_window_bits < client_max_window_bits: + client_max_window_bits = self.client_max_window_bits + + return ( + _build_parameters( + server_no_context_takeover, + client_no_context_takeover, + server_max_window_bits, + client_max_window_bits, + ), + PerMessageDeflate( + client_no_context_takeover, # remote_no_context_takeover + server_no_context_takeover, # local_no_context_takeover + client_max_window_bits or 15, # remote_max_window_bits + server_max_window_bits or 15, # local_max_window_bits + self.compress_settings, + ), + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/framing.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/framing.py new file mode 100644 index 00000000000..26e58cdbfbb --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/framing.py @@ -0,0 +1,342 @@ +""" +:mod:`websockets.framing` reads and writes WebSocket frames. + +It deals with a single frame at a time. Anything that depends on the sequence +of frames is implemented in :mod:`websockets.protocol`. + +See `section 5 of RFC 6455`_. + +.. _section 5 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-5 + +""" + +import io +import random +import struct +from typing import Any, Awaitable, Callable, NamedTuple, Optional, Sequence, Tuple + +from .exceptions import PayloadTooBig, ProtocolError +from .typing import Data + + +try: + from .speedups import apply_mask +except ImportError: # pragma: no cover + from .utils import apply_mask + + +__all__ = [ + "DATA_OPCODES", + "CTRL_OPCODES", + "OP_CONT", + "OP_TEXT", + "OP_BINARY", + "OP_CLOSE", + "OP_PING", + "OP_PONG", + "Frame", + "prepare_data", + "encode_data", + "parse_close", + "serialize_close", +] + +DATA_OPCODES = OP_CONT, OP_TEXT, OP_BINARY = 0x00, 0x01, 0x02 +CTRL_OPCODES = OP_CLOSE, OP_PING, OP_PONG = 0x08, 0x09, 0x0A + +# Close code that are allowed in a close frame. +# Using a list optimizes `code in EXTERNAL_CLOSE_CODES`. +EXTERNAL_CLOSE_CODES = [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011] + + +# Consider converting to a dataclass when dropping support for Python < 3.7. + + +class Frame(NamedTuple): + """ + WebSocket frame. + + :param bool fin: FIN bit + :param bool rsv1: RSV1 bit + :param bool rsv2: RSV2 bit + :param bool rsv3: RSV3 bit + :param int opcode: opcode + :param bytes data: payload data + + Only these fields are needed. The MASK bit, payload length and masking-key + are handled on the fly by :meth:`read` and :meth:`write`. + + """ + + fin: bool + opcode: int + data: bytes + rsv1: bool = False + rsv2: bool = False + rsv3: bool = False + + @classmethod + async def read( + cls, + reader: Callable[[int], Awaitable[bytes]], + *, + mask: bool, + max_size: Optional[int] = None, + extensions: Optional[Sequence["websockets.extensions.base.Extension"]] = None, + ) -> "Frame": + """ + Read a WebSocket frame. + + :param reader: coroutine that reads exactly the requested number of + bytes, unless the end of file is reached + :param mask: whether the frame should be masked i.e. whether the read + happens on the server side + :param max_size: maximum payload size in bytes + :param extensions: list of classes with a ``decode()`` method that + transforms the frame and return a new frame; extensions are applied + in reverse order + :raises ~websockets.exceptions.PayloadTooBig: if the frame exceeds + ``max_size`` + :raises ~websockets.exceptions.ProtocolError: if the frame + contains incorrect values + + """ + # Read the header. + data = await reader(2) + head1, head2 = struct.unpack("!BB", data) + + # While not Pythonic, this is marginally faster than calling bool(). + fin = True if head1 & 0b10000000 else False + rsv1 = True if head1 & 0b01000000 else False + rsv2 = True if head1 & 0b00100000 else False + rsv3 = True if head1 & 0b00010000 else False + opcode = head1 & 0b00001111 + + if (True if head2 & 0b10000000 else False) != mask: + raise ProtocolError("incorrect masking") + + length = head2 & 0b01111111 + if length == 126: + data = await reader(2) + (length,) = struct.unpack("!H", data) + elif length == 127: + data = await reader(8) + (length,) = struct.unpack("!Q", data) + if max_size is not None and length > max_size: + raise PayloadTooBig( + f"payload length exceeds size limit ({length} > {max_size} bytes)" + ) + if mask: + mask_bits = await reader(4) + + # Read the data. + data = await reader(length) + if mask: + data = apply_mask(data, mask_bits) + + frame = cls(fin, opcode, data, rsv1, rsv2, rsv3) + + if extensions is None: + extensions = [] + for extension in reversed(extensions): + frame = extension.decode(frame, max_size=max_size) + + frame.check() + + return frame + + def write( + frame, + write: Callable[[bytes], Any], + *, + mask: bool, + extensions: Optional[Sequence["websockets.extensions.base.Extension"]] = None, + ) -> None: + """ + Write a WebSocket frame. + + :param frame: frame to write + :param write: function that writes bytes + :param mask: whether the frame should be masked i.e. whether the write + happens on the client side + :param extensions: list of classes with an ``encode()`` method that + transform the frame and return a new frame; extensions are applied + in order + :raises ~websockets.exceptions.ProtocolError: if the frame + contains incorrect values + + """ + # The first parameter is called `frame` rather than `self`, + # but it's the instance of class to which this method is bound. + + frame.check() + + if extensions is None: + extensions = [] + for extension in extensions: + frame = extension.encode(frame) + + output = io.BytesIO() + + # Prepare the header. + head1 = ( + (0b10000000 if frame.fin else 0) + | (0b01000000 if frame.rsv1 else 0) + | (0b00100000 if frame.rsv2 else 0) + | (0b00010000 if frame.rsv3 else 0) + | frame.opcode + ) + + head2 = 0b10000000 if mask else 0 + + length = len(frame.data) + if length < 126: + output.write(struct.pack("!BB", head1, head2 | length)) + elif length < 65536: + output.write(struct.pack("!BBH", head1, head2 | 126, length)) + else: + output.write(struct.pack("!BBQ", head1, head2 | 127, length)) + + if mask: + mask_bits = struct.pack("!I", random.getrandbits(32)) + output.write(mask_bits) + + # Prepare the data. + if mask: + data = apply_mask(frame.data, mask_bits) + else: + data = frame.data + output.write(data) + + # Send the frame. + + # The frame is written in a single call to write in order to prevent + # TCP fragmentation. See #68 for details. This also makes it safe to + # send frames concurrently from multiple coroutines. + write(output.getvalue()) + + def check(frame) -> None: + """ + Check that reserved bits and opcode have acceptable values. + + :raises ~websockets.exceptions.ProtocolError: if a reserved + bit or the opcode is invalid + + """ + # The first parameter is called `frame` rather than `self`, + # but it's the instance of class to which this method is bound. + + if frame.rsv1 or frame.rsv2 or frame.rsv3: + raise ProtocolError("reserved bits must be 0") + + if frame.opcode in DATA_OPCODES: + return + elif frame.opcode in CTRL_OPCODES: + if len(frame.data) > 125: + raise ProtocolError("control frame too long") + if not frame.fin: + raise ProtocolError("fragmented control frame") + else: + raise ProtocolError(f"invalid opcode: {frame.opcode}") + + +def prepare_data(data: Data) -> Tuple[int, bytes]: + """ + Convert a string or byte-like object to an opcode and a bytes-like object. + + This function is designed for data frames. + + If ``data`` is a :class:`str`, return ``OP_TEXT`` and a :class:`bytes` + object encoding ``data`` in UTF-8. + + If ``data`` is a bytes-like object, return ``OP_BINARY`` and a bytes-like + object. + + :raises TypeError: if ``data`` doesn't have a supported type + + """ + if isinstance(data, str): + return OP_TEXT, data.encode("utf-8") + elif isinstance(data, (bytes, bytearray)): + return OP_BINARY, data + elif isinstance(data, memoryview): + if data.c_contiguous: + return OP_BINARY, data + else: + return OP_BINARY, data.tobytes() + else: + raise TypeError("data must be bytes-like or str") + + +def encode_data(data: Data) -> bytes: + """ + Convert a string or byte-like object to bytes. + + This function is designed for ping and pong frames. + + If ``data`` is a :class:`str`, return a :class:`bytes` object encoding + ``data`` in UTF-8. + + If ``data`` is a bytes-like object, return a :class:`bytes` object. + + :raises TypeError: if ``data`` doesn't have a supported type + + """ + if isinstance(data, str): + return data.encode("utf-8") + elif isinstance(data, (bytes, bytearray)): + return bytes(data) + elif isinstance(data, memoryview): + return data.tobytes() + else: + raise TypeError("data must be bytes-like or str") + + +def parse_close(data: bytes) -> Tuple[int, str]: + """ + Parse the payload from a close frame. + + Return ``(code, reason)``. + + :raises ~websockets.exceptions.ProtocolError: if data is ill-formed + :raises UnicodeDecodeError: if the reason isn't valid UTF-8 + + """ + length = len(data) + if length >= 2: + (code,) = struct.unpack("!H", data[:2]) + check_close(code) + reason = data[2:].decode("utf-8") + return code, reason + elif length == 0: + return 1005, "" + else: + assert length == 1 + raise ProtocolError("close frame too short") + + +def serialize_close(code: int, reason: str) -> bytes: + """ + Serialize the payload for a close frame. + + This is the reverse of :func:`parse_close`. + + """ + check_close(code) + return struct.pack("!H", code) + reason.encode("utf-8") + + +def check_close(code: int) -> None: + """ + Check that the close code has an acceptable value for a close frame. + + :raises ~websockets.exceptions.ProtocolError: if the close code + is invalid + + """ + if not (code in EXTERNAL_CLOSE_CODES or 3000 <= code < 5000): + raise ProtocolError("invalid status code") + + +# at the bottom to allow circular import, because Extension depends on Frame +import websockets.extensions.base # isort:skip # noqa diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/handshake.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/handshake.py new file mode 100644 index 00000000000..9bfe27754fa --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/handshake.py @@ -0,0 +1,185 @@ +""" +:mod:`websockets.handshake` provides helpers for the WebSocket handshake. + +See `section 4 of RFC 6455`_. + +.. _section 4 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-4 + +Some checks cannot be performed because they depend too much on the +context; instead, they're documented below. + +To accept a connection, a server must: + +- Read the request, check that the method is GET, and check the headers with + :func:`check_request`, +- Send a 101 response to the client with the headers created by + :func:`build_response` if the request is valid; otherwise, send an + appropriate HTTP error code. + +To open a connection, a client must: + +- Send a GET request to the server with the headers created by + :func:`build_request`, +- Read the response, check that the status code is 101, and check the headers + with :func:`check_response`. + +""" + +import base64 +import binascii +import hashlib +import random +from typing import List + +from .exceptions import InvalidHeader, InvalidHeaderValue, InvalidUpgrade +from .headers import ConnectionOption, UpgradeProtocol, parse_connection, parse_upgrade +from .http import Headers, MultipleValuesError + + +__all__ = ["build_request", "check_request", "build_response", "check_response"] + +GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +def build_request(headers: Headers) -> str: + """ + Build a handshake request to send to the server. + + Update request headers passed in argument. + + :param headers: request headers + :returns: ``key`` which must be passed to :func:`check_response` + + """ + raw_key = bytes(random.getrandbits(8) for _ in range(16)) + key = base64.b64encode(raw_key).decode() + headers["Upgrade"] = "websocket" + headers["Connection"] = "Upgrade" + headers["Sec-WebSocket-Key"] = key + headers["Sec-WebSocket-Version"] = "13" + return key + + +def check_request(headers: Headers) -> str: + """ + Check a handshake request received from the client. + + This function doesn't verify that the request is an HTTP/1.1 or higher GET + request and doesn't perform ``Host`` and ``Origin`` checks. These controls + are usually performed earlier in the HTTP request handling code. They're + the responsibility of the caller. + + :param headers: request headers + :returns: ``key`` which must be passed to :func:`build_response` + :raises ~websockets.exceptions.InvalidHandshake: if the handshake request + is invalid; then the server must return 400 Bad Request error + + """ + connection: List[ConnectionOption] = sum( + [parse_connection(value) for value in headers.get_all("Connection")], [] + ) + + if not any(value.lower() == "upgrade" for value in connection): + raise InvalidUpgrade("Connection", ", ".join(connection)) + + upgrade: List[UpgradeProtocol] = sum( + [parse_upgrade(value) for value in headers.get_all("Upgrade")], [] + ) + + # For compatibility with non-strict implementations, ignore case when + # checking the Upgrade header. It's supposed to be 'WebSocket'. + if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"): + raise InvalidUpgrade("Upgrade", ", ".join(upgrade)) + + try: + s_w_key = headers["Sec-WebSocket-Key"] + except KeyError: + raise InvalidHeader("Sec-WebSocket-Key") + except MultipleValuesError: + raise InvalidHeader( + "Sec-WebSocket-Key", "more than one Sec-WebSocket-Key header found" + ) + + try: + raw_key = base64.b64decode(s_w_key.encode(), validate=True) + except binascii.Error: + raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key) + if len(raw_key) != 16: + raise InvalidHeaderValue("Sec-WebSocket-Key", s_w_key) + + try: + s_w_version = headers["Sec-WebSocket-Version"] + except KeyError: + raise InvalidHeader("Sec-WebSocket-Version") + except MultipleValuesError: + raise InvalidHeader( + "Sec-WebSocket-Version", "more than one Sec-WebSocket-Version header found" + ) + + if s_w_version != "13": + raise InvalidHeaderValue("Sec-WebSocket-Version", s_w_version) + + return s_w_key + + +def build_response(headers: Headers, key: str) -> None: + """ + Build a handshake response to send to the client. + + Update response headers passed in argument. + + :param headers: response headers + :param key: comes from :func:`check_request` + + """ + headers["Upgrade"] = "websocket" + headers["Connection"] = "Upgrade" + headers["Sec-WebSocket-Accept"] = accept(key) + + +def check_response(headers: Headers, key: str) -> None: + """ + Check a handshake response received from the server. + + This function doesn't verify that the response is an HTTP/1.1 or higher + response with a 101 status code. These controls are the responsibility of + the caller. + + :param headers: response headers + :param key: comes from :func:`build_request` + :raises ~websockets.exceptions.InvalidHandshake: if the handshake response + is invalid + + """ + connection: List[ConnectionOption] = sum( + [parse_connection(value) for value in headers.get_all("Connection")], [] + ) + + if not any(value.lower() == "upgrade" for value in connection): + raise InvalidUpgrade("Connection", " ".join(connection)) + + upgrade: List[UpgradeProtocol] = sum( + [parse_upgrade(value) for value in headers.get_all("Upgrade")], [] + ) + + # For compatibility with non-strict implementations, ignore case when + # checking the Upgrade header. It's supposed to be 'WebSocket'. + if not (len(upgrade) == 1 and upgrade[0].lower() == "websocket"): + raise InvalidUpgrade("Upgrade", ", ".join(upgrade)) + + try: + s_w_accept = headers["Sec-WebSocket-Accept"] + except KeyError: + raise InvalidHeader("Sec-WebSocket-Accept") + except MultipleValuesError: + raise InvalidHeader( + "Sec-WebSocket-Accept", "more than one Sec-WebSocket-Accept header found" + ) + + if s_w_accept != accept(key): + raise InvalidHeaderValue("Sec-WebSocket-Accept", s_w_accept) + + +def accept(key: str) -> str: + sha1 = hashlib.sha1((key + GUID).encode()).digest() + return base64.b64encode(sha1).decode() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/headers.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/headers.py new file mode 100644 index 00000000000..f33c94c0464 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/headers.py @@ -0,0 +1,515 @@ +""" +:mod:`websockets.headers` provides parsers and serializers for HTTP headers +used in WebSocket handshake messages. + +These APIs cannot be imported from :mod:`websockets`. They must be imported +from :mod:`websockets.headers`. + +""" + +import base64 +import binascii +import re +from typing import Callable, List, NewType, Optional, Sequence, Tuple, TypeVar, cast + +from .exceptions import InvalidHeaderFormat, InvalidHeaderValue +from .typing import ExtensionHeader, ExtensionName, ExtensionParameter, Subprotocol + + +__all__ = [ + "parse_connection", + "parse_upgrade", + "parse_extension", + "build_extension", + "parse_subprotocol", + "build_subprotocol", + "build_www_authenticate_basic", + "parse_authorization_basic", + "build_authorization_basic", +] + + +T = TypeVar("T") + +ConnectionOption = NewType("ConnectionOption", str) +UpgradeProtocol = NewType("UpgradeProtocol", str) + + +# To avoid a dependency on a parsing library, we implement manually the ABNF +# described in https://tools.ietf.org/html/rfc6455#section-9.1 with the +# definitions from https://tools.ietf.org/html/rfc7230#appendix-B. + + +def peek_ahead(header: str, pos: int) -> Optional[str]: + """ + Return the next character from ``header`` at the given position. + + Return ``None`` at the end of ``header``. + + We never need to peek more than one character ahead. + + """ + return None if pos == len(header) else header[pos] + + +_OWS_re = re.compile(r"[\t ]*") + + +def parse_OWS(header: str, pos: int) -> int: + """ + Parse optional whitespace from ``header`` at the given position. + + Return the new position. + + The whitespace itself isn't returned because it isn't significant. + + """ + # There's always a match, possibly empty, whose content doesn't matter. + match = _OWS_re.match(header, pos) + assert match is not None + return match.end() + + +_token_re = re.compile(r"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+") + + +def parse_token(header: str, pos: int, header_name: str) -> Tuple[str, int]: + """ + Parse a token from ``header`` at the given position. + + Return the token value and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + match = _token_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected token", header, pos) + return match.group(), match.end() + + +_quoted_string_re = re.compile( + r'"(?:[\x09\x20-\x21\x23-\x5b\x5d-\x7e]|\\[\x09\x20-\x7e\x80-\xff])*"' +) + + +_unquote_re = re.compile(r"\\([\x09\x20-\x7e\x80-\xff])") + + +def parse_quoted_string(header: str, pos: int, header_name: str) -> Tuple[str, int]: + """ + Parse a quoted string from ``header`` at the given position. + + Return the unquoted value and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + match = _quoted_string_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected quoted string", header, pos) + return _unquote_re.sub(r"\1", match.group()[1:-1]), match.end() + + +_quotable_re = re.compile(r"[\x09\x20-\x7e\x80-\xff]*") + + +_quote_re = re.compile(r"([\x22\x5c])") + + +def build_quoted_string(value: str) -> str: + """ + Format ``value`` as a quoted string. + + This is the reverse of :func:`parse_quoted_string`. + + """ + match = _quotable_re.fullmatch(value) + if match is None: + raise ValueError("invalid characters for quoted-string encoding") + return '"' + _quote_re.sub(r"\\\1", value) + '"' + + +def parse_list( + parse_item: Callable[[str, int, str], Tuple[T, int]], + header: str, + pos: int, + header_name: str, +) -> List[T]: + """ + Parse a comma-separated list from ``header`` at the given position. + + This is appropriate for parsing values with the following grammar: + + 1#item + + ``parse_item`` parses one item. + + ``header`` is assumed not to start or end with whitespace. + + (This function is designed for parsing an entire header value and + :func:`~websockets.http.read_headers` strips whitespace from values.) + + Return a list of items. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + # Per https://tools.ietf.org/html/rfc7230#section-7, "a recipient MUST + # parse and ignore a reasonable number of empty list elements"; hence + # while loops that remove extra delimiters. + + # Remove extra delimiters before the first item. + while peek_ahead(header, pos) == ",": + pos = parse_OWS(header, pos + 1) + + items = [] + while True: + # Loop invariant: a item starts at pos in header. + item, pos = parse_item(header, pos, header_name) + items.append(item) + pos = parse_OWS(header, pos) + + # We may have reached the end of the header. + if pos == len(header): + break + + # There must be a delimiter after each element except the last one. + if peek_ahead(header, pos) == ",": + pos = parse_OWS(header, pos + 1) + else: + raise InvalidHeaderFormat(header_name, "expected comma", header, pos) + + # Remove extra delimiters before the next item. + while peek_ahead(header, pos) == ",": + pos = parse_OWS(header, pos + 1) + + # We may have reached the end of the header. + if pos == len(header): + break + + # Since we only advance in the header by one character with peek_ahead() + # or with the end position of a regex match, we can't overshoot the end. + assert pos == len(header) + + return items + + +def parse_connection_option( + header: str, pos: int, header_name: str +) -> Tuple[ConnectionOption, int]: + """ + Parse a Connection option from ``header`` at the given position. + + Return the protocol value and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + item, pos = parse_token(header, pos, header_name) + return cast(ConnectionOption, item), pos + + +def parse_connection(header: str) -> List[ConnectionOption]: + """ + Parse a ``Connection`` header. + + Return a list of HTTP connection options. + + :param header: value of the ``Connection`` header + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + return parse_list(parse_connection_option, header, 0, "Connection") + + +_protocol_re = re.compile( + r"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+(?:/[-!#$%&\'*+.^_`|~0-9a-zA-Z]+)?" +) + + +def parse_upgrade_protocol( + header: str, pos: int, header_name: str +) -> Tuple[UpgradeProtocol, int]: + """ + Parse an Upgrade protocol from ``header`` at the given position. + + Return the protocol value and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + match = _protocol_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected protocol", header, pos) + return cast(UpgradeProtocol, match.group()), match.end() + + +def parse_upgrade(header: str) -> List[UpgradeProtocol]: + """ + Parse an ``Upgrade`` header. + + Return a list of HTTP protocols. + + :param header: value of the ``Upgrade`` header + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + return parse_list(parse_upgrade_protocol, header, 0, "Upgrade") + + +def parse_extension_item_param( + header: str, pos: int, header_name: str +) -> Tuple[ExtensionParameter, int]: + """ + Parse a single extension parameter from ``header`` at the given position. + + Return a ``(name, value)`` pair and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + # Extract parameter name. + name, pos = parse_token(header, pos, header_name) + pos = parse_OWS(header, pos) + # Extract parameter value, if there is one. + value: Optional[str] = None + if peek_ahead(header, pos) == "=": + pos = parse_OWS(header, pos + 1) + if peek_ahead(header, pos) == '"': + pos_before = pos # for proper error reporting below + value, pos = parse_quoted_string(header, pos, header_name) + # https://tools.ietf.org/html/rfc6455#section-9.1 says: the value + # after quoted-string unescaping MUST conform to the 'token' ABNF. + if _token_re.fullmatch(value) is None: + raise InvalidHeaderFormat( + header_name, "invalid quoted header content", header, pos_before + ) + else: + value, pos = parse_token(header, pos, header_name) + pos = parse_OWS(header, pos) + + return (name, value), pos + + +def parse_extension_item( + header: str, pos: int, header_name: str +) -> Tuple[ExtensionHeader, int]: + """ + Parse an extension definition from ``header`` at the given position. + + Return an ``(extension name, parameters)`` pair, where ``parameters`` is a + list of ``(name, value)`` pairs, and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + # Extract extension name. + name, pos = parse_token(header, pos, header_name) + pos = parse_OWS(header, pos) + # Extract all parameters. + parameters = [] + while peek_ahead(header, pos) == ";": + pos = parse_OWS(header, pos + 1) + parameter, pos = parse_extension_item_param(header, pos, header_name) + parameters.append(parameter) + return (cast(ExtensionName, name), parameters), pos + + +def parse_extension(header: str) -> List[ExtensionHeader]: + """ + Parse a ``Sec-WebSocket-Extensions`` header. + + Return a list of WebSocket extensions and their parameters in this format:: + + [ + ( + 'extension name', + [ + ('parameter name', 'parameter value'), + .... + ] + ), + ... + ] + + Parameter values are ``None`` when no value is provided. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + return parse_list(parse_extension_item, header, 0, "Sec-WebSocket-Extensions") + + +parse_extension_list = parse_extension # alias for backwards compatibility + + +def build_extension_item( + name: ExtensionName, parameters: List[ExtensionParameter] +) -> str: + """ + Build an extension definition. + + This is the reverse of :func:`parse_extension_item`. + + """ + return "; ".join( + [cast(str, name)] + + [ + # Quoted strings aren't necessary because values are always tokens. + name if value is None else f"{name}={value}" + for name, value in parameters + ] + ) + + +def build_extension(extensions: Sequence[ExtensionHeader]) -> str: + """ + Build a ``Sec-WebSocket-Extensions`` header. + + This is the reverse of :func:`parse_extension`. + + """ + return ", ".join( + build_extension_item(name, parameters) for name, parameters in extensions + ) + + +build_extension_list = build_extension # alias for backwards compatibility + + +def parse_subprotocol_item( + header: str, pos: int, header_name: str +) -> Tuple[Subprotocol, int]: + """ + Parse a subprotocol from ``header`` at the given position. + + Return the subprotocol value and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + item, pos = parse_token(header, pos, header_name) + return cast(Subprotocol, item), pos + + +def parse_subprotocol(header: str) -> List[Subprotocol]: + """ + Parse a ``Sec-WebSocket-Protocol`` header. + + Return a list of WebSocket subprotocols. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + return parse_list(parse_subprotocol_item, header, 0, "Sec-WebSocket-Protocol") + + +parse_subprotocol_list = parse_subprotocol # alias for backwards compatibility + + +def build_subprotocol(protocols: Sequence[Subprotocol]) -> str: + """ + Build a ``Sec-WebSocket-Protocol`` header. + + This is the reverse of :func:`parse_subprotocol`. + + """ + return ", ".join(protocols) + + +build_subprotocol_list = build_subprotocol # alias for backwards compatibility + + +def build_www_authenticate_basic(realm: str) -> str: + """ + Build a ``WWW-Authenticate`` header for HTTP Basic Auth. + + :param realm: authentication realm + + """ + # https://tools.ietf.org/html/rfc7617#section-2 + realm = build_quoted_string(realm) + charset = build_quoted_string("UTF-8") + return f"Basic realm={realm}, charset={charset}" + + +_token68_re = re.compile(r"[A-Za-z0-9-._~+/]+=*") + + +def parse_token68(header: str, pos: int, header_name: str) -> Tuple[str, int]: + """ + Parse a token68 from ``header`` at the given position. + + Return the token value and the new position. + + :raises ~websockets.exceptions.InvalidHeaderFormat: on invalid inputs. + + """ + match = _token68_re.match(header, pos) + if match is None: + raise InvalidHeaderFormat(header_name, "expected token68", header, pos) + return match.group(), match.end() + + +def parse_end(header: str, pos: int, header_name: str) -> None: + """ + Check that parsing reached the end of header. + + """ + if pos < len(header): + raise InvalidHeaderFormat(header_name, "trailing data", header, pos) + + +def parse_authorization_basic(header: str) -> Tuple[str, str]: + """ + Parse an ``Authorization`` header for HTTP Basic Auth. + + Return a ``(username, password)`` tuple. + + :param header: value of the ``Authorization`` header + :raises InvalidHeaderFormat: on invalid inputs + :raises InvalidHeaderValue: on unsupported inputs + + """ + # https://tools.ietf.org/html/rfc7235#section-2.1 + # https://tools.ietf.org/html/rfc7617#section-2 + scheme, pos = parse_token(header, 0, "Authorization") + if scheme.lower() != "basic": + raise InvalidHeaderValue("Authorization", f"unsupported scheme: {scheme}") + if peek_ahead(header, pos) != " ": + raise InvalidHeaderFormat( + "Authorization", "expected space after scheme", header, pos + ) + pos += 1 + basic_credentials, pos = parse_token68(header, pos, "Authorization") + parse_end(header, pos, "Authorization") + + try: + user_pass = base64.b64decode(basic_credentials.encode()).decode() + except binascii.Error: + raise InvalidHeaderValue( + "Authorization", "expected base64-encoded credentials" + ) from None + try: + username, password = user_pass.split(":", 1) + except ValueError: + raise InvalidHeaderValue( + "Authorization", "expected username:password credentials" + ) from None + + return username, password + + +def build_authorization_basic(username: str, password: str) -> str: + """ + Build an ``Authorization`` header for HTTP Basic Auth. + + This is the reverse of :func:`parse_authorization_basic`. + + """ + # https://tools.ietf.org/html/rfc7617#section-2 + assert ":" not in username + user_pass = f"{username}:{password}" + basic_credentials = base64.b64encode(user_pass.encode()).decode() + return "Basic " + basic_credentials diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/http.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/http.py new file mode 100644 index 00000000000..ba6d274bf14 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/http.py @@ -0,0 +1,360 @@ +""" +:mod:`websockets.http` module provides basic HTTP/1.1 support. It is merely +:adequate for WebSocket handshake messages. + +These APIs cannot be imported from :mod:`websockets`. They must be imported +from :mod:`websockets.http`. + +""" + +import asyncio +import re +import sys +from typing import ( + Any, + Dict, + Iterable, + Iterator, + List, + Mapping, + MutableMapping, + Tuple, + Union, +) + +from .version import version as websockets_version + + +__all__ = [ + "read_request", + "read_response", + "Headers", + "MultipleValuesError", + "USER_AGENT", +] + +MAX_HEADERS = 256 +MAX_LINE = 4096 + +USER_AGENT = f"Python/{sys.version[:3]} websockets/{websockets_version}" + + +def d(value: bytes) -> str: + """ + Decode a bytestring for interpolating into an error message. + + """ + return value.decode(errors="backslashreplace") + + +# See https://tools.ietf.org/html/rfc7230#appendix-B. + +# Regex for validating header names. + +_token_re = re.compile(rb"[-!#$%&\'*+.^_`|~0-9a-zA-Z]+") + +# Regex for validating header values. + +# We don't attempt to support obsolete line folding. + +# Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff). + +# The ABNF is complicated because it attempts to express that optional +# whitespace is ignored. We strip whitespace and don't revalidate that. + +# See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 + +_value_re = re.compile(rb"[\x09\x20-\x7e\x80-\xff]*") + + +async def read_request(stream: asyncio.StreamReader) -> Tuple[str, "Headers"]: + """ + Read an HTTP/1.1 GET request and return ``(path, headers)``. + + ``path`` isn't URL-decoded or validated in any way. + + ``path`` and ``headers`` are expected to contain only ASCII characters. + Other characters are represented with surrogate escapes. + + :func:`read_request` doesn't attempt to read the request body because + WebSocket handshake requests don't have one. If the request contains a + body, it may be read from ``stream`` after this coroutine returns. + + :param stream: input to read the request from + :raises EOFError: if the connection is closed without a full HTTP request + :raises SecurityError: if the request exceeds a security limit + :raises ValueError: if the request isn't well formatted + + """ + # https://tools.ietf.org/html/rfc7230#section-3.1.1 + + # Parsing is simple because fixed values are expected for method and + # version and because path isn't checked. Since WebSocket software tends + # to implement HTTP/1.1 strictly, there's little need for lenient parsing. + + try: + request_line = await read_line(stream) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP request line") from exc + + try: + method, raw_path, version = request_line.split(b" ", 2) + except ValueError: # not enough values to unpack (expected 3, got 1-2) + raise ValueError(f"invalid HTTP request line: {d(request_line)}") from None + + if method != b"GET": + raise ValueError(f"unsupported HTTP method: {d(method)}") + if version != b"HTTP/1.1": + raise ValueError(f"unsupported HTTP version: {d(version)}") + path = raw_path.decode("ascii", "surrogateescape") + + headers = await read_headers(stream) + + return path, headers + + +async def read_response(stream: asyncio.StreamReader) -> Tuple[int, str, "Headers"]: + """ + Read an HTTP/1.1 response and return ``(status_code, reason, headers)``. + + ``reason`` and ``headers`` are expected to contain only ASCII characters. + Other characters are represented with surrogate escapes. + + :func:`read_request` doesn't attempt to read the response body because + WebSocket handshake responses don't have one. If the response contains a + body, it may be read from ``stream`` after this coroutine returns. + + :param stream: input to read the response from + :raises EOFError: if the connection is closed without a full HTTP response + :raises SecurityError: if the response exceeds a security limit + :raises ValueError: if the response isn't well formatted + + """ + # https://tools.ietf.org/html/rfc7230#section-3.1.2 + + # As in read_request, parsing is simple because a fixed value is expected + # for version, status_code is a 3-digit number, and reason can be ignored. + + try: + status_line = await read_line(stream) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP status line") from exc + + try: + version, raw_status_code, raw_reason = status_line.split(b" ", 2) + except ValueError: # not enough values to unpack (expected 3, got 1-2) + raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None + + if version != b"HTTP/1.1": + raise ValueError(f"unsupported HTTP version: {d(version)}") + try: + status_code = int(raw_status_code) + except ValueError: # invalid literal for int() with base 10 + raise ValueError(f"invalid HTTP status code: {d(raw_status_code)}") from None + if not 100 <= status_code < 1000: + raise ValueError(f"unsupported HTTP status code: {d(raw_status_code)}") + if not _value_re.fullmatch(raw_reason): + raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}") + reason = raw_reason.decode() + + headers = await read_headers(stream) + + return status_code, reason, headers + + +async def read_headers(stream: asyncio.StreamReader) -> "Headers": + """ + Read HTTP headers from ``stream``. + + Non-ASCII characters are represented with surrogate escapes. + + """ + # https://tools.ietf.org/html/rfc7230#section-3.2 + + # We don't attempt to support obsolete line folding. + + headers = Headers() + for _ in range(MAX_HEADERS + 1): + try: + line = await read_line(stream) + except EOFError as exc: + raise EOFError("connection closed while reading HTTP headers") from exc + if line == b"": + break + + try: + raw_name, raw_value = line.split(b":", 1) + except ValueError: # not enough values to unpack (expected 2, got 1) + raise ValueError(f"invalid HTTP header line: {d(line)}") from None + if not _token_re.fullmatch(raw_name): + raise ValueError(f"invalid HTTP header name: {d(raw_name)}") + raw_value = raw_value.strip(b" \t") + if not _value_re.fullmatch(raw_value): + raise ValueError(f"invalid HTTP header value: {d(raw_value)}") + + name = raw_name.decode("ascii") # guaranteed to be ASCII at this point + value = raw_value.decode("ascii", "surrogateescape") + headers[name] = value + + else: + raise websockets.exceptions.SecurityError("too many HTTP headers") + + return headers + + +async def read_line(stream: asyncio.StreamReader) -> bytes: + """ + Read a single line from ``stream``. + + CRLF is stripped from the return value. + + """ + # Security: this is bounded by the StreamReader's limit (default = 32 KiB). + line = await stream.readline() + # Security: this guarantees header values are small (hard-coded = 4 KiB) + if len(line) > MAX_LINE: + raise websockets.exceptions.SecurityError("line too long") + # Not mandatory but safe - https://tools.ietf.org/html/rfc7230#section-3.5 + if not line.endswith(b"\r\n"): + raise EOFError("line without CRLF") + return line[:-2] + + +class MultipleValuesError(LookupError): + """ + Exception raised when :class:`Headers` has more than one value for a key. + + """ + + def __str__(self) -> str: + # Implement the same logic as KeyError_str in Objects/exceptions.c. + if len(self.args) == 1: + return repr(self.args[0]) + return super().__str__() + + +class Headers(MutableMapping[str, str]): + """ + Efficient data structure for manipulating HTTP headers. + + A :class:`list` of ``(name, values)`` is inefficient for lookups. + + A :class:`dict` doesn't suffice because header names are case-insensitive + and multiple occurrences of headers with the same name are possible. + + :class:`Headers` stores HTTP headers in a hybrid data structure to provide + efficient insertions and lookups while preserving the original data. + + In order to account for multiple values with minimal hassle, + :class:`Headers` follows this logic: + + - When getting a header with ``headers[name]``: + - if there's no value, :exc:`KeyError` is raised; + - if there's exactly one value, it's returned; + - if there's more than one value, :exc:`MultipleValuesError` is raised. + + - When setting a header with ``headers[name] = value``, the value is + appended to the list of values for that header. + + - When deleting a header with ``del headers[name]``, all values for that + header are removed (this is slow). + + Other methods for manipulating headers are consistent with this logic. + + As long as no header occurs multiple times, :class:`Headers` behaves like + :class:`dict`, except keys are lower-cased to provide case-insensitivity. + + Two methods support support manipulating multiple values explicitly: + + - :meth:`get_all` returns a list of all values for a header; + - :meth:`raw_items` returns an iterator of ``(name, values)`` pairs. + + """ + + __slots__ = ["_dict", "_list"] + + def __init__(self, *args: Any, **kwargs: str) -> None: + self._dict: Dict[str, List[str]] = {} + self._list: List[Tuple[str, str]] = [] + # MutableMapping.update calls __setitem__ for each (name, value) pair. + self.update(*args, **kwargs) + + def __str__(self) -> str: + return "".join(f"{key}: {value}\r\n" for key, value in self._list) + "\r\n" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self._list!r})" + + def copy(self) -> "Headers": + copy = self.__class__() + copy._dict = self._dict.copy() + copy._list = self._list.copy() + return copy + + # Collection methods + + def __contains__(self, key: object) -> bool: + return isinstance(key, str) and key.lower() in self._dict + + def __iter__(self) -> Iterator[str]: + return iter(self._dict) + + def __len__(self) -> int: + return len(self._dict) + + # MutableMapping methods + + def __getitem__(self, key: str) -> str: + value = self._dict[key.lower()] + if len(value) == 1: + return value[0] + else: + raise MultipleValuesError(key) + + def __setitem__(self, key: str, value: str) -> None: + self._dict.setdefault(key.lower(), []).append(value) + self._list.append((key, value)) + + def __delitem__(self, key: str) -> None: + key_lower = key.lower() + self._dict.__delitem__(key_lower) + # This is inefficent. Fortunately deleting HTTP headers is uncommon. + self._list = [(k, v) for k, v in self._list if k.lower() != key_lower] + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Headers): + return NotImplemented + return self._list == other._list + + def clear(self) -> None: + """ + Remove all headers. + + """ + self._dict = {} + self._list = [] + + # Methods for handling multiple values + + def get_all(self, key: str) -> List[str]: + """ + Return the (possibly empty) list of all values for a header. + + :param key: header name + + """ + return self._dict.get(key.lower(), []) + + def raw_items(self) -> Iterator[Tuple[str, str]]: + """ + Return an iterator of all values as ``(name, value)`` pairs. + + """ + return iter(self._list) + + +HeadersLike = Union[Headers, Mapping[str, str], Iterable[Tuple[str, str]]] + + +# at the bottom to allow circular import, because AbortHandshake depends on HeadersLike +import websockets.exceptions # isort:skip # noqa diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/protocol.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/protocol.py new file mode 100644 index 00000000000..6c29b2a5240 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/protocol.py @@ -0,0 +1,1429 @@ +""" +:mod:`websockets.protocol` handles WebSocket control and data frames. + +See `sections 4 to 8 of RFC 6455`_. + +.. _sections 4 to 8 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-4 + +""" + +import asyncio +import codecs +import collections +import enum +import logging +import random +import struct +import sys +import warnings +from typing import ( + Any, + AsyncIterable, + AsyncIterator, + Awaitable, + Deque, + Dict, + Iterable, + List, + Optional, + Union, + cast, +) + +from .exceptions import ( + ConnectionClosed, + ConnectionClosedError, + ConnectionClosedOK, + InvalidState, + PayloadTooBig, + ProtocolError, +) +from .extensions.base import Extension +from .framing import * +from .handshake import * +from .http import Headers +from .typing import Data + + +__all__ = ["WebSocketCommonProtocol"] + +logger = logging.getLogger(__name__) + + +# A WebSocket connection goes through the following four states, in order: + + +class State(enum.IntEnum): + CONNECTING, OPEN, CLOSING, CLOSED = range(4) + + +# In order to ensure consistency, the code always checks the current value of +# WebSocketCommonProtocol.state before assigning a new value and never yields +# between the check and the assignment. + + +class WebSocketCommonProtocol(asyncio.Protocol): + """ + :class:`~asyncio.Protocol` subclass implementing the data transfer phase. + + Once the WebSocket connection is established, during the data transfer + phase, the protocol is almost symmetrical between the server side and the + client side. :class:`WebSocketCommonProtocol` implements logic that's + shared between servers and clients.. + + Subclasses such as :class:`~websockets.server.WebSocketServerProtocol` and + :class:`~websockets.client.WebSocketClientProtocol` implement the opening + handshake, which is different between servers and clients. + + :class:`WebSocketCommonProtocol` performs four functions: + + * It runs a task that stores incoming data frames in a queue and makes + them available with the :meth:`recv` coroutine. + * It sends outgoing data frames with the :meth:`send` coroutine. + * It deals with control frames automatically. + * It performs the closing handshake. + + :class:`WebSocketCommonProtocol` supports asynchronous iteration:: + + async for message in websocket: + await process(message) + + The iterator yields incoming messages. It exits normally when the + connection is closed with the close code 1000 (OK) or 1001 (going away). + It raises a :exc:`~websockets.exceptions.ConnectionClosedError` exception + when the connection is closed with any other code. + + Once the connection is open, a `Ping frame`_ is sent every + ``ping_interval`` seconds. This serves as a keepalive. It helps keeping + the connection open, especially in the presence of proxies with short + timeouts on inactive connections. Set ``ping_interval`` to ``None`` to + disable this behavior. + + .. _Ping frame: https://tools.ietf.org/html/rfc6455#section-5.5.2 + + If the corresponding `Pong frame`_ isn't received within ``ping_timeout`` + seconds, the connection is considered unusable and is closed with + code 1011. This ensures that the remote endpoint remains responsive. Set + ``ping_timeout`` to ``None`` to disable this behavior. + + .. _Pong frame: https://tools.ietf.org/html/rfc6455#section-5.5.3 + + The ``close_timeout`` parameter defines a maximum wait time in seconds for + completing the closing handshake and terminating the TCP connection. + :meth:`close` completes in at most ``4 * close_timeout`` on the server + side and ``5 * close_timeout`` on the client side. + + ``close_timeout`` needs to be a parameter of the protocol because + ``websockets`` usually calls :meth:`close` implicitly: + + - on the server side, when the connection handler terminates, + - on the client side, when exiting the context manager for the connection. + + To apply a timeout to any other API, wrap it in :func:`~asyncio.wait_for`. + + The ``max_size`` parameter enforces the maximum size for incoming messages + in bytes. The default value is 1 MiB. ``None`` disables the limit. If a + message larger than the maximum size is received, :meth:`recv` will + raise :exc:`~websockets.exceptions.ConnectionClosedError` and the + connection will be closed with code 1009. + + The ``max_queue`` parameter sets the maximum length of the queue that + holds incoming messages. The default value is ``32``. ``None`` disables + the limit. Messages are added to an in-memory queue when they're received; + then :meth:`recv` pops from that queue. In order to prevent excessive + memory consumption when messages are received faster than they can be + processed, the queue must be bounded. If the queue fills up, the protocol + stops processing incoming data until :meth:`recv` is called. In this + situation, various receive buffers (at least in ``asyncio`` and in the OS) + will fill up, then the TCP receive window will shrink, slowing down + transmission to avoid packet loss. + + Since Python can use up to 4 bytes of memory to represent a single + character, each connection may use up to ``4 * max_size * max_queue`` + bytes of memory to store incoming messages. By default, this is 128 MiB. + You may want to lower the limits, depending on your application's + requirements. + + The ``read_limit`` argument sets the high-water limit of the buffer for + incoming bytes. The low-water limit is half the high-water limit. The + default value is 64 KiB, half of asyncio's default (based on the current + implementation of :class:`~asyncio.StreamReader`). + + The ``write_limit`` argument sets the high-water limit of the buffer for + outgoing bytes. The low-water limit is a quarter of the high-water limit. + The default value is 64 KiB, equal to asyncio's default (based on the + current implementation of ``FlowControlMixin``). + + As soon as the HTTP request and response in the opening handshake are + processed: + + * the request path is available in the :attr:`path` attribute; + * the request and response HTTP headers are available in the + :attr:`request_headers` and :attr:`response_headers` attributes, + which are :class:`~websockets.http.Headers` instances. + + If a subprotocol was negotiated, it's available in the :attr:`subprotocol` + attribute. + + Once the connection is closed, the code is available in the + :attr:`close_code` attribute and the reason in :attr:`close_reason`. + + All these attributes must be treated as read-only. + + """ + + # There are only two differences between the client-side and server-side + # behavior: masking the payload and closing the underlying TCP connection. + # Set is_client = True/False and side = "client"/"server" to pick a side. + is_client: bool + side: str = "undefined" + + def __init__( + self, + *, + ping_interval: Optional[float] = 20, + ping_timeout: Optional[float] = 20, + close_timeout: Optional[float] = None, + max_size: Optional[int] = 2 ** 20, + max_queue: Optional[int] = 2 ** 5, + read_limit: int = 2 ** 16, + write_limit: int = 2 ** 16, + loop: Optional[asyncio.AbstractEventLoop] = None, + # The following arguments are kept only for backwards compatibility. + host: Optional[str] = None, + port: Optional[int] = None, + secure: Optional[bool] = None, + legacy_recv: bool = False, + timeout: Optional[float] = None, + ) -> None: + # Backwards compatibility: close_timeout used to be called timeout. + if timeout is None: + timeout = 10 + else: + warnings.warn("rename timeout to close_timeout", DeprecationWarning) + # If both are specified, timeout is ignored. + if close_timeout is None: + close_timeout = timeout + + self.ping_interval = ping_interval + self.ping_timeout = ping_timeout + self.close_timeout = close_timeout + self.max_size = max_size + self.max_queue = max_queue + self.read_limit = read_limit + self.write_limit = write_limit + + if loop is None: + loop = asyncio.get_event_loop() + self.loop = loop + + self._host = host + self._port = port + self._secure = secure + self.legacy_recv = legacy_recv + + # Configure read buffer limits. The high-water limit is defined by + # ``self.read_limit``. The ``limit`` argument controls the line length + # limit and half the buffer limit of :class:`~asyncio.StreamReader`. + # That's why it must be set to half of ``self.read_limit``. + self.reader = asyncio.StreamReader(limit=read_limit // 2, loop=loop) + + # Copied from asyncio.FlowControlMixin + self._paused = False + self._drain_waiter: Optional[asyncio.Future[None]] = None + + self._drain_lock = asyncio.Lock( + loop=loop if sys.version_info[:2] < (3, 8) else None + ) + + # This class implements the data transfer and closing handshake, which + # are shared between the client-side and the server-side. + # Subclasses implement the opening handshake and, on success, execute + # :meth:`connection_open` to change the state to OPEN. + self.state = State.CONNECTING + logger.debug("%s - state = CONNECTING", self.side) + + # HTTP protocol parameters. + self.path: str + self.request_headers: Headers + self.response_headers: Headers + + # WebSocket protocol parameters. + self.extensions: List[Extension] = [] + self.subprotocol: Optional[str] = None + + # The close code and reason are set when receiving a close frame or + # losing the TCP connection. + self.close_code: int + self.close_reason: str + + # Completed when the connection state becomes CLOSED. Translates the + # :meth:`connection_lost` callback to a :class:`~asyncio.Future` + # that can be awaited. (Other :class:`~asyncio.Protocol` callbacks are + # translated by ``self.stream_reader``). + self.connection_lost_waiter: asyncio.Future[None] = loop.create_future() + + # Queue of received messages. + self.messages: Deque[Data] = collections.deque() + self._pop_message_waiter: Optional[asyncio.Future[None]] = None + self._put_message_waiter: Optional[asyncio.Future[None]] = None + + # Protect sending fragmented messages. + self._fragmented_message_waiter: Optional[asyncio.Future[None]] = None + + # Mapping of ping IDs to waiters, in chronological order. + self.pings: Dict[bytes, asyncio.Future[None]] = {} + + # Task running the data transfer. + self.transfer_data_task: asyncio.Task[None] + + # Exception that occurred during data transfer, if any. + self.transfer_data_exc: Optional[BaseException] = None + + # Task sending keepalive pings. + self.keepalive_ping_task: asyncio.Task[None] + + # Task closing the TCP connection. + self.close_connection_task: asyncio.Task[None] + + # Copied from asyncio.FlowControlMixin + async def _drain_helper(self) -> None: # pragma: no cover + if self.connection_lost_waiter.done(): + raise ConnectionResetError("Connection lost") + if not self._paused: + return + waiter = self._drain_waiter + assert waiter is None or waiter.cancelled() + waiter = self.loop.create_future() + self._drain_waiter = waiter + await waiter + + # Copied from asyncio.StreamWriter + async def _drain(self) -> None: # pragma: no cover + if self.reader is not None: + exc = self.reader.exception() + if exc is not None: + raise exc + if self.transport is not None: + if self.transport.is_closing(): + # Yield to the event loop so connection_lost() may be + # called. Without this, _drain_helper() would return + # immediately, and code that calls + # write(...); yield from drain() + # in a loop would never call connection_lost(), so it + # would not see an error when the socket is closed. + await asyncio.sleep( + 0, loop=self.loop if sys.version_info[:2] < (3, 8) else None + ) + await self._drain_helper() + + def connection_open(self) -> None: + """ + Callback when the WebSocket opening handshake completes. + + Enter the OPEN state and start the data transfer phase. + + """ + # 4.1. The WebSocket Connection is Established. + assert self.state is State.CONNECTING + self.state = State.OPEN + logger.debug("%s - state = OPEN", self.side) + # Start the task that receives incoming WebSocket messages. + self.transfer_data_task = self.loop.create_task(self.transfer_data()) + # Start the task that sends pings at regular intervals. + self.keepalive_ping_task = self.loop.create_task(self.keepalive_ping()) + # Start the task that eventually closes the TCP connection. + self.close_connection_task = self.loop.create_task(self.close_connection()) + + @property + def host(self) -> Optional[str]: + alternative = "remote_address" if self.is_client else "local_address" + warnings.warn(f"use {alternative}[0] instead of host", DeprecationWarning) + return self._host + + @property + def port(self) -> Optional[int]: + alternative = "remote_address" if self.is_client else "local_address" + warnings.warn(f"use {alternative}[1] instead of port", DeprecationWarning) + return self._port + + @property + def secure(self) -> Optional[bool]: + warnings.warn(f"don't use secure", DeprecationWarning) + return self._secure + + # Public API + + @property + def local_address(self) -> Any: + """ + Local address of the connection. + + This is a ``(host, port)`` tuple or ``None`` if the connection hasn't + been established yet. + + """ + try: + transport = self.transport + except AttributeError: + return None + else: + return transport.get_extra_info("sockname") + + @property + def remote_address(self) -> Any: + """ + Remote address of the connection. + + This is a ``(host, port)`` tuple or ``None`` if the connection hasn't + been established yet. + + """ + try: + transport = self.transport + except AttributeError: + return None + else: + return transport.get_extra_info("peername") + + @property + def open(self) -> bool: + """ + ``True`` when the connection is usable. + + It may be used to detect disconnections. However, this approach is + discouraged per the EAFP_ principle. + + When ``open`` is ``False``, using the connection raises a + :exc:`~websockets.exceptions.ConnectionClosed` exception. + + .. _EAFP: https://docs.python.org/3/glossary.html#term-eafp + + """ + return self.state is State.OPEN and not self.transfer_data_task.done() + + @property + def closed(self) -> bool: + """ + ``True`` once the connection is closed. + + Be aware that both :attr:`open` and :attr:`closed` are ``False`` during + the opening and closing sequences. + + """ + return self.state is State.CLOSED + + async def wait_closed(self) -> None: + """ + Wait until the connection is closed. + + This is identical to :attr:`closed`, except it can be awaited. + + This can make it easier to handle connection termination, regardless + of its cause, in tasks that interact with the WebSocket connection. + + """ + await asyncio.shield(self.connection_lost_waiter) + + async def __aiter__(self) -> AsyncIterator[Data]: + """ + Iterate on received messages. + + Exit normally when the connection is closed with code 1000 or 1001. + + Raise an exception in other cases. + + """ + try: + while True: + yield await self.recv() + except ConnectionClosedOK: + return + + async def recv(self) -> Data: + """ + Receive the next message. + + Return a :class:`str` for a text frame and :class:`bytes` for a binary + frame. + + When the end of the message stream is reached, :meth:`recv` raises + :exc:`~websockets.exceptions.ConnectionClosed`. Specifically, it + raises :exc:`~websockets.exceptions.ConnectionClosedOK` after a normal + connection closure and + :exc:`~websockets.exceptions.ConnectionClosedError` after a protocol + error or a network failure. + + .. versionchanged:: 3.0 + + :meth:`recv` used to return ``None`` instead. Refer to the + changelog for details. + + Canceling :meth:`recv` is safe. There's no risk of losing the next + message. The next invocation of :meth:`recv` will return it. This + makes it possible to enforce a timeout by wrapping :meth:`recv` in + :func:`~asyncio.wait_for`. + + :raises ~websockets.exceptions.ConnectionClosed: when the + connection is closed + :raises RuntimeError: if two coroutines call :meth:`recv` concurrently + + """ + if self._pop_message_waiter is not None: + raise RuntimeError( + "cannot call recv while another coroutine " + "is already waiting for the next message" + ) + + # Don't await self.ensure_open() here: + # - messages could be available in the queue even if the connection + # is closed; + # - messages could be received before the closing frame even if the + # connection is closing. + + # Wait until there's a message in the queue (if necessary) or the + # connection is closed. + while len(self.messages) <= 0: + pop_message_waiter: asyncio.Future[None] = self.loop.create_future() + self._pop_message_waiter = pop_message_waiter + try: + # If asyncio.wait() is canceled, it doesn't cancel + # pop_message_waiter and self.transfer_data_task. + await asyncio.wait( + [pop_message_waiter, self.transfer_data_task], + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + return_when=asyncio.FIRST_COMPLETED, + ) + finally: + self._pop_message_waiter = None + + # If asyncio.wait(...) exited because self.transfer_data_task + # completed before receiving a new message, raise a suitable + # exception (or return None if legacy_recv is enabled). + if not pop_message_waiter.done(): + if self.legacy_recv: + return None # type: ignore + else: + # Wait until the connection is closed to raise + # ConnectionClosed with the correct code and reason. + await self.ensure_open() + + # Pop a message from the queue. + message = self.messages.popleft() + + # Notify transfer_data(). + if self._put_message_waiter is not None: + self._put_message_waiter.set_result(None) + self._put_message_waiter = None + + return message + + async def send( + self, message: Union[Data, Iterable[Data], AsyncIterable[Data]] + ) -> None: + """ + Send a message. + + A string (:class:`str`) is sent as a `Text frame`_. A bytestring or + bytes-like object (:class:`bytes`, :class:`bytearray`, or + :class:`memoryview`) is sent as a `Binary frame`_. + + .. _Text frame: https://tools.ietf.org/html/rfc6455#section-5.6 + .. _Binary frame: https://tools.ietf.org/html/rfc6455#section-5.6 + + :meth:`send` also accepts an iterable or an asynchronous iterable of + strings, bytestrings, or bytes-like objects. In that case the message + is fragmented. Each item is treated as a message fragment and sent in + its own frame. All items must be of the same type, or else + :meth:`send` will raise a :exc:`TypeError` and the connection will be + closed. + + Canceling :meth:`send` is discouraged. Instead, you should close the + connection with :meth:`close`. Indeed, there only two situations where + :meth:`send` yields control to the event loop: + + 1. The write buffer is full. If you don't want to wait until enough + data is sent, your only alternative is to close the connection. + :meth:`close` will likely time out then abort the TCP connection. + 2. ``message`` is an asynchronous iterator. Stopping in the middle of + a fragmented message will cause a protocol error. Closing the + connection has the same effect. + + :raises TypeError: for unsupported inputs + + """ + await self.ensure_open() + + # While sending a fragmented message, prevent sending other messages + # until all fragments are sent. + while self._fragmented_message_waiter is not None: + await asyncio.shield(self._fragmented_message_waiter) + + # Unfragmented message -- this case must be handled first because + # strings and bytes-like objects are iterable. + + if isinstance(message, (str, bytes, bytearray, memoryview)): + opcode, data = prepare_data(message) + await self.write_frame(True, opcode, data) + + # Fragmented message -- regular iterator. + + elif isinstance(message, Iterable): + + # Work around https://github.com/python/mypy/issues/6227 + message = cast(Iterable[Data], message) + + iter_message = iter(message) + try: + message_chunk = next(iter_message) + except StopIteration: + return + opcode, data = prepare_data(message_chunk) + + self._fragmented_message_waiter = asyncio.Future() + try: + # First fragment. + await self.write_frame(False, opcode, data) + + # Other fragments. + for message_chunk in iter_message: + confirm_opcode, data = prepare_data(message_chunk) + if confirm_opcode != opcode: + raise TypeError("data contains inconsistent types") + await self.write_frame(False, OP_CONT, data) + + # Final fragment. + await self.write_frame(True, OP_CONT, b"") + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + self.fail_connection(1011) + raise + + finally: + self._fragmented_message_waiter.set_result(None) + self._fragmented_message_waiter = None + + # Fragmented message -- asynchronous iterator + + elif isinstance(message, AsyncIterable): + # aiter_message = aiter(message) without aiter + # https://github.com/python/mypy/issues/5738 + aiter_message = type(message).__aiter__(message) # type: ignore + try: + # message_chunk = anext(aiter_message) without anext + # https://github.com/python/mypy/issues/5738 + message_chunk = await type(aiter_message).__anext__( # type: ignore + aiter_message + ) + except StopAsyncIteration: + return + opcode, data = prepare_data(message_chunk) + + self._fragmented_message_waiter = asyncio.Future() + try: + # First fragment. + await self.write_frame(False, opcode, data) + + # Other fragments. + # https://github.com/python/mypy/issues/5738 + async for message_chunk in aiter_message: # type: ignore + confirm_opcode, data = prepare_data(message_chunk) + if confirm_opcode != opcode: + raise TypeError("data contains inconsistent types") + await self.write_frame(False, OP_CONT, data) + + # Final fragment. + await self.write_frame(True, OP_CONT, b"") + + except Exception: + # We're half-way through a fragmented message and we can't + # complete it. This makes the connection unusable. + self.fail_connection(1011) + raise + + finally: + self._fragmented_message_waiter.set_result(None) + self._fragmented_message_waiter = None + + else: + raise TypeError("data must be bytes, str, or iterable") + + async def close(self, code: int = 1000, reason: str = "") -> None: + """ + Perform the closing handshake. + + :meth:`close` waits for the other end to complete the handshake and + for the TCP connection to terminate. As a consequence, there's no need + to await :meth:`wait_closed`; :meth:`close` already does it. + + :meth:`close` is idempotent: it doesn't do anything once the + connection is closed. + + Wrapping :func:`close` in :func:`~asyncio.create_task` is safe, given + that errors during connection termination aren't particularly useful. + + Canceling :meth:`close` is discouraged. If it takes too long, you can + set a shorter ``close_timeout``. If you don't want to wait, let the + Python process exit, then the OS will close the TCP connection. + + :param code: WebSocket close code + :param reason: WebSocket close reason + + """ + try: + await asyncio.wait_for( + self.write_close_frame(serialize_close(code, reason)), + self.close_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except asyncio.TimeoutError: + # If the close frame cannot be sent because the send buffers + # are full, the closing handshake won't complete anyway. + # Fail the connection to shut down faster. + self.fail_connection() + + # If no close frame is received within the timeout, wait_for() cancels + # the data transfer task and raises TimeoutError. + + # If close() is called multiple times concurrently and one of these + # calls hits the timeout, the data transfer task will be cancelled. + # Other calls will receive a CancelledError here. + + try: + # If close() is canceled during the wait, self.transfer_data_task + # is canceled before the timeout elapses. + await asyncio.wait_for( + self.transfer_data_task, + self.close_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except (asyncio.TimeoutError, asyncio.CancelledError): + pass + + # Wait for the close connection task to close the TCP connection. + await asyncio.shield(self.close_connection_task) + + async def ping(self, data: Optional[Data] = None) -> Awaitable[None]: + """ + Send a ping. + + Return a :class:`~asyncio.Future` which will be completed when the + corresponding pong is received and which you may ignore if you don't + want to wait. + + A ping may serve as a keepalive or as a check that the remote endpoint + received all messages up to this point:: + + pong_waiter = await ws.ping() + await pong_waiter # only if you want to wait for the pong + + By default, the ping contains four random bytes. This payload may be + overridden with the optional ``data`` argument which must be a string + (which will be encoded to UTF-8) or a bytes-like object. + + Canceling :meth:`ping` is discouraged. If :meth:`ping` doesn't return + immediately, it means the write buffer is full. If you don't want to + wait, you should close the connection. + + Canceling the :class:`~asyncio.Future` returned by :meth:`ping` has no + effect. + + """ + await self.ensure_open() + + if data is not None: + data = encode_data(data) + + # Protect against duplicates if a payload is explicitly set. + if data in self.pings: + raise ValueError("already waiting for a pong with the same data") + + # Generate a unique random payload otherwise. + while data is None or data in self.pings: + data = struct.pack("!I", random.getrandbits(32)) + + self.pings[data] = self.loop.create_future() + + await self.write_frame(True, OP_PING, data) + + return asyncio.shield(self.pings[data]) + + async def pong(self, data: Data = b"") -> None: + """ + Send a pong. + + An unsolicited pong may serve as a unidirectional heartbeat. + + The payload may be set with the optional ``data`` argument which must + be a string (which will be encoded to UTF-8) or a bytes-like object. + + Canceling :meth:`pong` is discouraged for the same reason as + :meth:`ping`. + + """ + await self.ensure_open() + + data = encode_data(data) + + await self.write_frame(True, OP_PONG, data) + + # Private methods - no guarantees. + + def connection_closed_exc(self) -> ConnectionClosed: + exception: ConnectionClosed + if self.close_code == 1000 or self.close_code == 1001: + exception = ConnectionClosedOK(self.close_code, self.close_reason) + else: + exception = ConnectionClosedError(self.close_code, self.close_reason) + # Chain to the exception that terminated data transfer, if any. + exception.__cause__ = self.transfer_data_exc + return exception + + async def ensure_open(self) -> None: + """ + Check that the WebSocket connection is open. + + Raise :exc:`~websockets.exceptions.ConnectionClosed` if it isn't. + + """ + # Handle cases from most common to least common for performance. + if self.state is State.OPEN: + # If self.transfer_data_task exited without a closing handshake, + # self.close_connection_task may be closing the connection, going + # straight from OPEN to CLOSED. + if self.transfer_data_task.done(): + await asyncio.shield(self.close_connection_task) + raise self.connection_closed_exc() + else: + return + + if self.state is State.CLOSED: + raise self.connection_closed_exc() + + if self.state is State.CLOSING: + # If we started the closing handshake, wait for its completion to + # get the proper close code and reason. self.close_connection_task + # will complete within 4 or 5 * close_timeout after close(). The + # CLOSING state also occurs when failing the connection. In that + # case self.close_connection_task will complete even faster. + await asyncio.shield(self.close_connection_task) + raise self.connection_closed_exc() + + # Control may only reach this point in buggy third-party subclasses. + assert self.state is State.CONNECTING + raise InvalidState("WebSocket connection isn't established yet") + + async def transfer_data(self) -> None: + """ + Read incoming messages and put them in a queue. + + This coroutine runs in a task until the closing handshake is started. + + """ + try: + while True: + message = await self.read_message() + + # Exit the loop when receiving a close frame. + if message is None: + break + + # Wait until there's room in the queue (if necessary). + if self.max_queue is not None: + while len(self.messages) >= self.max_queue: + self._put_message_waiter = self.loop.create_future() + try: + await asyncio.shield(self._put_message_waiter) + finally: + self._put_message_waiter = None + + # Put the message in the queue. + self.messages.append(message) + + # Notify recv(). + if self._pop_message_waiter is not None: + self._pop_message_waiter.set_result(None) + self._pop_message_waiter = None + + except asyncio.CancelledError as exc: + self.transfer_data_exc = exc + # If fail_connection() cancels this task, avoid logging the error + # twice and failing the connection again. + raise + + except ProtocolError as exc: + self.transfer_data_exc = exc + self.fail_connection(1002) + + except (ConnectionError, EOFError) as exc: + # Reading data with self.reader.readexactly may raise: + # - most subclasses of ConnectionError if the TCP connection + # breaks, is reset, or is aborted; + # - IncompleteReadError, a subclass of EOFError, if fewer + # bytes are available than requested. + self.transfer_data_exc = exc + self.fail_connection(1006) + + except UnicodeDecodeError as exc: + self.transfer_data_exc = exc + self.fail_connection(1007) + + except PayloadTooBig as exc: + self.transfer_data_exc = exc + self.fail_connection(1009) + + except Exception as exc: + # This shouldn't happen often because exceptions expected under + # regular circumstances are handled above. If it does, consider + # catching and handling more exceptions. + logger.error("Error in data transfer", exc_info=True) + + self.transfer_data_exc = exc + self.fail_connection(1011) + + async def read_message(self) -> Optional[Data]: + """ + Read a single message from the connection. + + Re-assemble data frames if the message is fragmented. + + Return ``None`` when the closing handshake is started. + + """ + frame = await self.read_data_frame(max_size=self.max_size) + + # A close frame was received. + if frame is None: + return None + + if frame.opcode == OP_TEXT: + text = True + elif frame.opcode == OP_BINARY: + text = False + else: # frame.opcode == OP_CONT + raise ProtocolError("unexpected opcode") + + # Shortcut for the common case - no fragmentation + if frame.fin: + return frame.data.decode("utf-8") if text else frame.data + + # 5.4. Fragmentation + chunks: List[Data] = [] + max_size = self.max_size + if text: + decoder_factory = codecs.getincrementaldecoder("utf-8") + decoder = decoder_factory(errors="strict") + if max_size is None: + + def append(frame: Frame) -> None: + nonlocal chunks + chunks.append(decoder.decode(frame.data, frame.fin)) + + else: + + def append(frame: Frame) -> None: + nonlocal chunks, max_size + chunks.append(decoder.decode(frame.data, frame.fin)) + assert isinstance(max_size, int) + max_size -= len(frame.data) + + else: + if max_size is None: + + def append(frame: Frame) -> None: + nonlocal chunks + chunks.append(frame.data) + + else: + + def append(frame: Frame) -> None: + nonlocal chunks, max_size + chunks.append(frame.data) + assert isinstance(max_size, int) + max_size -= len(frame.data) + + append(frame) + + while not frame.fin: + frame = await self.read_data_frame(max_size=max_size) + if frame is None: + raise ProtocolError("incomplete fragmented message") + if frame.opcode != OP_CONT: + raise ProtocolError("unexpected opcode") + append(frame) + + # mypy cannot figure out that chunks have the proper type. + return ("" if text else b"").join(chunks) # type: ignore + + async def read_data_frame(self, max_size: Optional[int]) -> Optional[Frame]: + """ + Read a single data frame from the connection. + + Process control frames received before the next data frame. + + Return ``None`` if a close frame is encountered before any data frame. + + """ + # 6.2. Receiving Data + while True: + frame = await self.read_frame(max_size) + + # 5.5. Control Frames + if frame.opcode == OP_CLOSE: + # 7.1.5. The WebSocket Connection Close Code + # 7.1.6. The WebSocket Connection Close Reason + self.close_code, self.close_reason = parse_close(frame.data) + try: + # Echo the original data instead of re-serializing it with + # serialize_close() because that fails when the close frame + # is empty and parse_close() synthetizes a 1005 close code. + await self.write_close_frame(frame.data) + except ConnectionClosed: + # It doesn't really matter if the connection was closed + # before we could send back a close frame. + pass + return None + + elif frame.opcode == OP_PING: + # Answer pings. + ping_hex = frame.data.hex() or "[empty]" + logger.debug( + "%s - received ping, sending pong: %s", self.side, ping_hex + ) + await self.pong(frame.data) + + elif frame.opcode == OP_PONG: + # Acknowledge pings on solicited pongs. + if frame.data in self.pings: + logger.debug( + "%s - received solicited pong: %s", + self.side, + frame.data.hex() or "[empty]", + ) + # Acknowledge all pings up to the one matching this pong. + ping_id = None + ping_ids = [] + for ping_id, ping in self.pings.items(): + ping_ids.append(ping_id) + if not ping.done(): + ping.set_result(None) + if ping_id == frame.data: + break + else: # pragma: no cover + assert False, "ping_id is in self.pings" + # Remove acknowledged pings from self.pings. + for ping_id in ping_ids: + del self.pings[ping_id] + ping_ids = ping_ids[:-1] + if ping_ids: + pings_hex = ", ".join( + ping_id.hex() or "[empty]" for ping_id in ping_ids + ) + plural = "s" if len(ping_ids) > 1 else "" + logger.debug( + "%s - acknowledged previous ping%s: %s", + self.side, + plural, + pings_hex, + ) + else: + logger.debug( + "%s - received unsolicited pong: %s", + self.side, + frame.data.hex() or "[empty]", + ) + + # 5.6. Data Frames + else: + return frame + + async def read_frame(self, max_size: Optional[int]) -> Frame: + """ + Read a single frame from the connection. + + """ + frame = await Frame.read( + self.reader.readexactly, + mask=not self.is_client, + max_size=max_size, + extensions=self.extensions, + ) + logger.debug("%s < %r", self.side, frame) + return frame + + async def write_frame( + self, fin: bool, opcode: int, data: bytes, *, _expected_state: int = State.OPEN + ) -> None: + # Defensive assertion for protocol compliance. + if self.state is not _expected_state: # pragma: no cover + raise InvalidState( + f"Cannot write to a WebSocket in the {self.state.name} state" + ) + + frame = Frame(fin, opcode, data) + logger.debug("%s > %r", self.side, frame) + frame.write( + self.transport.write, mask=self.is_client, extensions=self.extensions + ) + + try: + # drain() cannot be called concurrently by multiple coroutines: + # http://bugs.python.org/issue29930. Remove this lock when no + # version of Python where this bugs exists is supported anymore. + async with self._drain_lock: + # Handle flow control automatically. + await self._drain() + except ConnectionError: + # Terminate the connection if the socket died. + self.fail_connection() + # Wait until the connection is closed to raise ConnectionClosed + # with the correct code and reason. + await self.ensure_open() + + async def write_close_frame(self, data: bytes = b"") -> None: + """ + Write a close frame if and only if the connection state is OPEN. + + This dedicated coroutine must be used for writing close frames to + ensure that at most one close frame is sent on a given connection. + + """ + # Test and set the connection state before sending the close frame to + # avoid sending two frames in case of concurrent calls. + if self.state is State.OPEN: + # 7.1.3. The WebSocket Closing Handshake is Started + self.state = State.CLOSING + logger.debug("%s - state = CLOSING", self.side) + + # 7.1.2. Start the WebSocket Closing Handshake + await self.write_frame(True, OP_CLOSE, data, _expected_state=State.CLOSING) + + async def keepalive_ping(self) -> None: + """ + Send a Ping frame and wait for a Pong frame at regular intervals. + + This coroutine exits when the connection terminates and one of the + following happens: + + - :meth:`ping` raises :exc:`ConnectionClosed`, or + - :meth:`close_connection` cancels :attr:`keepalive_ping_task`. + + """ + if self.ping_interval is None: + return + + try: + while True: + await asyncio.sleep( + self.ping_interval, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + + # ping() raises CancelledError if the connection is closed, + # when close_connection() cancels self.keepalive_ping_task. + + # ping() raises ConnectionClosed if the connection is lost, + # when connection_lost() calls abort_pings(). + + ping_waiter = await self.ping() + + if self.ping_timeout is not None: + try: + await asyncio.wait_for( + ping_waiter, + self.ping_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except asyncio.TimeoutError: + logger.debug("%s ! timed out waiting for pong", self.side) + self.fail_connection(1011) + break + + except asyncio.CancelledError: + raise + + except ConnectionClosed: + pass + + except Exception: + logger.warning("Unexpected exception in keepalive ping task", exc_info=True) + + async def close_connection(self) -> None: + """ + 7.1.1. Close the WebSocket Connection + + When the opening handshake succeeds, :meth:`connection_open` starts + this coroutine in a task. It waits for the data transfer phase to + complete then it closes the TCP connection cleanly. + + When the opening handshake fails, :meth:`fail_connection` does the + same. There's no data transfer phase in that case. + + """ + try: + # Wait for the data transfer phase to complete. + if hasattr(self, "transfer_data_task"): + try: + await self.transfer_data_task + except asyncio.CancelledError: + pass + + # Cancel the keepalive ping task. + if hasattr(self, "keepalive_ping_task"): + self.keepalive_ping_task.cancel() + + # A client should wait for a TCP close from the server. + if self.is_client and hasattr(self, "transfer_data_task"): + if await self.wait_for_connection_lost(): + return + logger.debug("%s ! timed out waiting for TCP close", self.side) + + # Half-close the TCP connection if possible (when there's no TLS). + if self.transport.can_write_eof(): + logger.debug("%s x half-closing TCP connection", self.side) + self.transport.write_eof() + + if await self.wait_for_connection_lost(): + return + logger.debug("%s ! timed out waiting for TCP close", self.side) + + finally: + # The try/finally ensures that the transport never remains open, + # even if this coroutine is canceled (for example). + + # If connection_lost() was called, the TCP connection is closed. + # However, if TLS is enabled, the transport still needs closing. + # Else asyncio complains: ResourceWarning: unclosed transport. + if self.connection_lost_waiter.done() and self.transport.is_closing(): + return + + # Close the TCP connection. Buffers are flushed asynchronously. + logger.debug("%s x closing TCP connection", self.side) + self.transport.close() + + if await self.wait_for_connection_lost(): + return + logger.debug("%s ! timed out waiting for TCP close", self.side) + + # Abort the TCP connection. Buffers are discarded. + logger.debug("%s x aborting TCP connection", self.side) + self.transport.abort() + + # connection_lost() is called quickly after aborting. + await self.wait_for_connection_lost() + + async def wait_for_connection_lost(self) -> bool: + """ + Wait until the TCP connection is closed or ``self.close_timeout`` elapses. + + Return ``True`` if the connection is closed and ``False`` otherwise. + + """ + if not self.connection_lost_waiter.done(): + try: + await asyncio.wait_for( + asyncio.shield(self.connection_lost_waiter), + self.close_timeout, + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + except asyncio.TimeoutError: + pass + # Re-check self.connection_lost_waiter.done() synchronously because + # connection_lost() could run between the moment the timeout occurs + # and the moment this coroutine resumes running. + return self.connection_lost_waiter.done() + + def fail_connection(self, code: int = 1006, reason: str = "") -> None: + """ + 7.1.7. Fail the WebSocket Connection + + This requires: + + 1. Stopping all processing of incoming data, which means cancelling + :attr:`transfer_data_task`. The close code will be 1006 unless a + close frame was received earlier. + + 2. Sending a close frame with an appropriate code if the opening + handshake succeeded and the other side is likely to process it. + + 3. Closing the connection. :meth:`close_connection` takes care of + this once :attr:`transfer_data_task` exits after being canceled. + + (The specification describes these steps in the opposite order.) + + """ + logger.debug( + "%s ! failing %s WebSocket connection with code %d", + self.side, + self.state.name, + code, + ) + + # Cancel transfer_data_task if the opening handshake succeeded. + # cancel() is idempotent and ignored if the task is done already. + if hasattr(self, "transfer_data_task"): + self.transfer_data_task.cancel() + + # Send a close frame when the state is OPEN (a close frame was already + # sent if it's CLOSING), except when failing the connection because of + # an error reading from or writing to the network. + # Don't send a close frame if the connection is broken. + if code != 1006 and self.state is State.OPEN: + + frame_data = serialize_close(code, reason) + + # Write the close frame without draining the write buffer. + + # Keeping fail_connection() synchronous guarantees it can't + # get stuck and simplifies the implementation of the callers. + # Not drainig the write buffer is acceptable in this context. + + # This duplicates a few lines of code from write_close_frame() + # and write_frame(). + + self.state = State.CLOSING + logger.debug("%s - state = CLOSING", self.side) + + frame = Frame(True, OP_CLOSE, frame_data) + logger.debug("%s > %r", self.side, frame) + frame.write( + self.transport.write, mask=self.is_client, extensions=self.extensions + ) + + # Start close_connection_task if the opening handshake didn't succeed. + if not hasattr(self, "close_connection_task"): + self.close_connection_task = self.loop.create_task(self.close_connection()) + + def abort_pings(self) -> None: + """ + Raise ConnectionClosed in pending keepalive pings. + + They'll never receive a pong once the connection is closed. + + """ + assert self.state is State.CLOSED + exc = self.connection_closed_exc() + + for ping in self.pings.values(): + ping.set_exception(exc) + # If the exception is never retrieved, it will be logged when ping + # is garbage-collected. This is confusing for users. + # Given that ping is done (with an exception), canceling it does + # nothing, but it prevents logging the exception. + ping.cancel() + + if self.pings: + pings_hex = ", ".join(ping_id.hex() or "[empty]" for ping_id in self.pings) + plural = "s" if len(self.pings) > 1 else "" + logger.debug( + "%s - aborted pending ping%s: %s", self.side, plural, pings_hex + ) + + # asyncio.Protocol methods + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + """ + Configure write buffer limits. + + The high-water limit is defined by ``self.write_limit``. + + The low-water limit currently defaults to ``self.write_limit // 4`` in + :meth:`~asyncio.WriteTransport.set_write_buffer_limits`, which should + be all right for reasonable use cases of this library. + + This is the earliest point where we can get hold of the transport, + which means it's the best point for configuring it. + + """ + logger.debug("%s - event = connection_made(%s)", self.side, transport) + + transport = cast(asyncio.Transport, transport) + transport.set_write_buffer_limits(self.write_limit) + self.transport = transport + + # Copied from asyncio.StreamReaderProtocol + self.reader.set_transport(transport) + + def connection_lost(self, exc: Optional[Exception]) -> None: + """ + 7.1.4. The WebSocket Connection is Closed. + + """ + logger.debug("%s - event = connection_lost(%s)", self.side, exc) + self.state = State.CLOSED + logger.debug("%s - state = CLOSED", self.side) + if not hasattr(self, "close_code"): + self.close_code = 1006 + if not hasattr(self, "close_reason"): + self.close_reason = "" + logger.debug( + "%s x code = %d, reason = %s", + self.side, + self.close_code, + self.close_reason or "[no reason]", + ) + self.abort_pings() + # If self.connection_lost_waiter isn't pending, that's a bug, because: + # - it's set only here in connection_lost() which is called only once; + # - it must never be canceled. + self.connection_lost_waiter.set_result(None) + + if True: # pragma: no cover + + # Copied from asyncio.StreamReaderProtocol + if self.reader is not None: + if exc is None: + self.reader.feed_eof() + else: + self.reader.set_exception(exc) + + # Copied from asyncio.FlowControlMixin + # Wake up the writer if currently paused. + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + return + self._drain_waiter = None + if waiter.done(): + return + if exc is None: + waiter.set_result(None) + else: + waiter.set_exception(exc) + + def pause_writing(self) -> None: # pragma: no cover + assert not self._paused + self._paused = True + + def resume_writing(self) -> None: # pragma: no cover + assert self._paused + self._paused = False + + waiter = self._drain_waiter + if waiter is not None: + self._drain_waiter = None + if not waiter.done(): + waiter.set_result(None) + + def data_received(self, data: bytes) -> None: + logger.debug("%s - event = data_received(<%d bytes>)", self.side, len(data)) + self.reader.feed_data(data) + + def eof_received(self) -> None: + """ + Close the transport after receiving EOF. + + The WebSocket protocol has its own closing handshake: endpoints close + the TCP or TLS connection after sending and receiving a close frame. + + As a consequence, they never need to write after receiving EOF, so + there's no reason to keep the transport open by returning ``True``. + + Besides, that doesn't work on TLS connections. + + """ + logger.debug("%s - event = eof_received()", self.side) + self.reader.feed_eof() diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/py.typed b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/py.typed new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/py.typed diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/server.py new file mode 100644 index 00000000000..4f5e9e0efe0 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/server.py @@ -0,0 +1,995 @@ +""" +:mod:`websockets.server` defines the WebSocket server APIs. + +""" + +import asyncio +import collections.abc +import email.utils +import functools +import http +import logging +import socket +import sys +import warnings +from types import TracebackType +from typing import ( + Any, + Awaitable, + Callable, + Generator, + List, + Optional, + Sequence, + Set, + Tuple, + Type, + Union, + cast, +) + +from .exceptions import ( + AbortHandshake, + InvalidHandshake, + InvalidHeader, + InvalidMessage, + InvalidOrigin, + InvalidUpgrade, + NegotiationError, +) +from .extensions.base import Extension, ServerExtensionFactory +from .extensions.permessage_deflate import ServerPerMessageDeflateFactory +from .handshake import build_response, check_request +from .headers import build_extension, parse_extension, parse_subprotocol +from .http import USER_AGENT, Headers, HeadersLike, MultipleValuesError, read_request +from .protocol import WebSocketCommonProtocol +from .typing import ExtensionHeader, Origin, Subprotocol + + +__all__ = ["serve", "unix_serve", "WebSocketServerProtocol", "WebSocketServer"] + +logger = logging.getLogger(__name__) + + +HeadersLikeOrCallable = Union[HeadersLike, Callable[[str, Headers], HeadersLike]] + +HTTPResponse = Tuple[http.HTTPStatus, HeadersLike, bytes] + + +class WebSocketServerProtocol(WebSocketCommonProtocol): + """ + :class:`~asyncio.Protocol` subclass implementing a WebSocket server. + + This class inherits most of its methods from + :class:`~websockets.protocol.WebSocketCommonProtocol`. + + For the sake of simplicity, it doesn't rely on a full HTTP implementation. + Its support for HTTP responses is very limited. + + """ + + is_client = False + side = "server" + + def __init__( + self, + ws_handler: Callable[["WebSocketServerProtocol", str], Awaitable[Any]], + ws_server: "WebSocketServer", + *, + origins: Optional[Sequence[Optional[Origin]]] = None, + extensions: Optional[Sequence[ServerExtensionFactory]] = None, + subprotocols: Optional[Sequence[Subprotocol]] = None, + extra_headers: Optional[HeadersLikeOrCallable] = None, + process_request: Optional[ + Callable[[str, Headers], Awaitable[Optional[HTTPResponse]]] + ] = None, + select_subprotocol: Optional[ + Callable[[Sequence[Subprotocol], Sequence[Subprotocol]], Subprotocol] + ] = None, + **kwargs: Any, + ) -> None: + # For backwards compatibility with 6.0 or earlier. + if origins is not None and "" in origins: + warnings.warn("use None instead of '' in origins", DeprecationWarning) + origins = [None if origin == "" else origin for origin in origins] + self.ws_handler = ws_handler + self.ws_server = ws_server + self.origins = origins + self.available_extensions = extensions + self.available_subprotocols = subprotocols + self.extra_headers = extra_headers + self._process_request = process_request + self._select_subprotocol = select_subprotocol + super().__init__(**kwargs) + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + """ + Register connection and initialize a task to handle it. + + """ + super().connection_made(transport) + # Register the connection with the server before creating the handler + # task. Registering at the beginning of the handler coroutine would + # create a race condition between the creation of the task, which + # schedules its execution, and the moment the handler starts running. + self.ws_server.register(self) + self.handler_task = self.loop.create_task(self.handler()) + + async def handler(self) -> None: + """ + Handle the lifecycle of a WebSocket connection. + + Since this method doesn't have a caller able to handle exceptions, it + attemps to log relevant ones and guarantees that the TCP connection is + closed before exiting. + + """ + try: + + try: + path = await self.handshake( + origins=self.origins, + available_extensions=self.available_extensions, + available_subprotocols=self.available_subprotocols, + extra_headers=self.extra_headers, + ) + except ConnectionError: + logger.debug("Connection error in opening handshake", exc_info=True) + raise + except Exception as exc: + if isinstance(exc, AbortHandshake): + status, headers, body = exc.status, exc.headers, exc.body + elif isinstance(exc, InvalidOrigin): + logger.debug("Invalid origin", exc_info=True) + status, headers, body = ( + http.HTTPStatus.FORBIDDEN, + Headers(), + f"Failed to open a WebSocket connection: {exc}.\n".encode(), + ) + elif isinstance(exc, InvalidUpgrade): + logger.debug("Invalid upgrade", exc_info=True) + status, headers, body = ( + http.HTTPStatus.UPGRADE_REQUIRED, + Headers([("Upgrade", "websocket")]), + ( + f"Failed to open a WebSocket connection: {exc}.\n" + f"\n" + f"You cannot access a WebSocket server directly " + f"with a browser. You need a WebSocket client.\n" + ).encode(), + ) + elif isinstance(exc, InvalidHandshake): + logger.debug("Invalid handshake", exc_info=True) + status, headers, body = ( + http.HTTPStatus.BAD_REQUEST, + Headers(), + f"Failed to open a WebSocket connection: {exc}.\n".encode(), + ) + else: + logger.warning("Error in opening handshake", exc_info=True) + status, headers, body = ( + http.HTTPStatus.INTERNAL_SERVER_ERROR, + Headers(), + ( + b"Failed to open a WebSocket connection.\n" + b"See server log for more information.\n" + ), + ) + + headers.setdefault("Date", email.utils.formatdate(usegmt=True)) + headers.setdefault("Server", USER_AGENT) + headers.setdefault("Content-Length", str(len(body))) + headers.setdefault("Content-Type", "text/plain") + headers.setdefault("Connection", "close") + + self.write_http_response(status, headers, body) + self.fail_connection() + await self.wait_closed() + return + + try: + await self.ws_handler(self, path) + except Exception: + logger.error("Error in connection handler", exc_info=True) + if not self.closed: + self.fail_connection(1011) + raise + + try: + await self.close() + except ConnectionError: + logger.debug("Connection error in closing handshake", exc_info=True) + raise + except Exception: + logger.warning("Error in closing handshake", exc_info=True) + raise + + except Exception: + # Last-ditch attempt to avoid leaking connections on errors. + try: + self.transport.close() + except Exception: # pragma: no cover + pass + + finally: + # Unregister the connection with the server when the handler task + # terminates. Registration is tied to the lifecycle of the handler + # task because the server waits for tasks attached to registered + # connections before terminating. + self.ws_server.unregister(self) + + async def read_http_request(self) -> Tuple[str, Headers]: + """ + Read request line and headers from the HTTP request. + + If the request contains a body, it may be read from ``self.reader`` + after this coroutine returns. + + :raises ~websockets.exceptions.InvalidMessage: if the HTTP message is + malformed or isn't an HTTP/1.1 GET request + + """ + try: + path, headers = await read_request(self.reader) + except Exception as exc: + raise InvalidMessage("did not receive a valid HTTP request") from exc + + logger.debug("%s < GET %s HTTP/1.1", self.side, path) + logger.debug("%s < %r", self.side, headers) + + self.path = path + self.request_headers = headers + + return path, headers + + def write_http_response( + self, status: http.HTTPStatus, headers: Headers, body: Optional[bytes] = None + ) -> None: + """ + Write status line and headers to the HTTP response. + + This coroutine is also able to write a response body. + + """ + self.response_headers = headers + + logger.debug("%s > HTTP/1.1 %d %s", self.side, status.value, status.phrase) + logger.debug("%s > %r", self.side, headers) + + # Since the status line and headers only contain ASCII characters, + # we can keep this simple. + response = f"HTTP/1.1 {status.value} {status.phrase}\r\n" + response += str(headers) + + self.transport.write(response.encode()) + + if body is not None: + logger.debug("%s > body (%d bytes)", self.side, len(body)) + self.transport.write(body) + + async def process_request( + self, path: str, request_headers: Headers + ) -> Optional[HTTPResponse]: + """ + Intercept the HTTP request and return an HTTP response if appropriate. + + If ``process_request`` returns ``None``, the WebSocket handshake + continues. If it returns 3-uple containing a status code, response + headers and a response body, that HTTP response is sent and the + connection is closed. In that case: + + * The HTTP status must be a :class:`~http.HTTPStatus`. + * HTTP headers must be a :class:`~websockets.http.Headers` instance, a + :class:`~collections.abc.Mapping`, or an iterable of ``(name, + value)`` pairs. + * The HTTP response body must be :class:`bytes`. It may be empty. + + This coroutine may be overridden in a :class:`WebSocketServerProtocol` + subclass, for example: + + * to return a HTTP 200 OK response on a given path; then a load + balancer can use this path for a health check; + * to authenticate the request and return a HTTP 401 Unauthorized or a + HTTP 403 Forbidden when authentication fails. + + Instead of subclassing, it is possible to override this method by + passing a ``process_request`` argument to the :func:`serve` function + or the :class:`WebSocketServerProtocol` constructor. This is + equivalent, except ``process_request`` won't have access to the + protocol instance, so it can't store information for later use. + + ``process_request`` is expected to complete quickly. If it may run for + a long time, then it should await :meth:`wait_closed` and exit if + :meth:`wait_closed` completes, or else it could prevent the server + from shutting down. + + :param path: request path, including optional query string + :param request_headers: request headers + + """ + if self._process_request is not None: + response = self._process_request(path, request_headers) + if isinstance(response, Awaitable): + return await response + else: + # For backwards compatibility with 7.0. + warnings.warn( + "declare process_request as a coroutine", DeprecationWarning + ) + return response # type: ignore + return None + + @staticmethod + def process_origin( + headers: Headers, origins: Optional[Sequence[Optional[Origin]]] = None + ) -> Optional[Origin]: + """ + Handle the Origin HTTP request header. + + :param headers: request headers + :param origins: optional list of acceptable origins + :raises ~websockets.exceptions.InvalidOrigin: if the origin isn't + acceptable + + """ + # "The user agent MUST NOT include more than one Origin header field" + # per https://tools.ietf.org/html/rfc6454#section-7.3. + try: + origin = cast(Origin, headers.get("Origin")) + except MultipleValuesError: + raise InvalidHeader("Origin", "more than one Origin header found") + if origins is not None: + if origin not in origins: + raise InvalidOrigin(origin) + return origin + + @staticmethod + def process_extensions( + headers: Headers, + available_extensions: Optional[Sequence[ServerExtensionFactory]], + ) -> Tuple[Optional[str], List[Extension]]: + """ + Handle the Sec-WebSocket-Extensions HTTP request header. + + Accept or reject each extension proposed in the client request. + Negotiate parameters for accepted extensions. + + Return the Sec-WebSocket-Extensions HTTP response header and the list + of accepted extensions. + + :rfc:`6455` leaves the rules up to the specification of each + :extension. + + To provide this level of flexibility, for each extension proposed by + the client, we check for a match with each extension available in the + server configuration. If no match is found, the extension is ignored. + + If several variants of the same extension are proposed by the client, + it may be accepted severel times, which won't make sense in general. + Extensions must implement their own requirements. For this purpose, + the list of previously accepted extensions is provided. + + This process doesn't allow the server to reorder extensions. It can + only select a subset of the extensions proposed by the client. + + Other requirements, for example related to mandatory extensions or the + order of extensions, may be implemented by overriding this method. + + :param headers: request headers + :param extensions: optional list of supported extensions + :raises ~websockets.exceptions.InvalidHandshake: to abort the + handshake with an HTTP 400 error code + + """ + response_header_value: Optional[str] = None + + extension_headers: List[ExtensionHeader] = [] + accepted_extensions: List[Extension] = [] + + header_values = headers.get_all("Sec-WebSocket-Extensions") + + if header_values and available_extensions: + + parsed_header_values: List[ExtensionHeader] = sum( + [parse_extension(header_value) for header_value in header_values], [] + ) + + for name, request_params in parsed_header_values: + + for ext_factory in available_extensions: + + # Skip non-matching extensions based on their name. + if ext_factory.name != name: + continue + + # Skip non-matching extensions based on their params. + try: + response_params, extension = ext_factory.process_request_params( + request_params, accepted_extensions + ) + except NegotiationError: + continue + + # Add matching extension to the final list. + extension_headers.append((name, response_params)) + accepted_extensions.append(extension) + + # Break out of the loop once we have a match. + break + + # If we didn't break from the loop, no extension in our list + # matched what the client sent. The extension is declined. + + # Serialize extension header. + if extension_headers: + response_header_value = build_extension(extension_headers) + + return response_header_value, accepted_extensions + + # Not @staticmethod because it calls self.select_subprotocol() + def process_subprotocol( + self, headers: Headers, available_subprotocols: Optional[Sequence[Subprotocol]] + ) -> Optional[Subprotocol]: + """ + Handle the Sec-WebSocket-Protocol HTTP request header. + + Return Sec-WebSocket-Protocol HTTP response header, which is the same + as the selected subprotocol. + + :param headers: request headers + :param available_subprotocols: optional list of supported subprotocols + :raises ~websockets.exceptions.InvalidHandshake: to abort the + handshake with an HTTP 400 error code + + """ + subprotocol: Optional[Subprotocol] = None + + header_values = headers.get_all("Sec-WebSocket-Protocol") + + if header_values and available_subprotocols: + + parsed_header_values: List[Subprotocol] = sum( + [parse_subprotocol(header_value) for header_value in header_values], [] + ) + + subprotocol = self.select_subprotocol( + parsed_header_values, available_subprotocols + ) + + return subprotocol + + def select_subprotocol( + self, + client_subprotocols: Sequence[Subprotocol], + server_subprotocols: Sequence[Subprotocol], + ) -> Optional[Subprotocol]: + """ + Pick a subprotocol among those offered by the client. + + If several subprotocols are supported by the client and the server, + the default implementation selects the preferred subprotocols by + giving equal value to the priorities of the client and the server. + + If no subprotocol is supported by the client and the server, it + proceeds without a subprotocol. + + This is unlikely to be the most useful implementation in practice, as + many servers providing a subprotocol will require that the client uses + that subprotocol. Such rules can be implemented in a subclass. + + Instead of subclassing, it is possible to override this method by + passing a ``select_subprotocol`` argument to the :func:`serve` + function or the :class:`WebSocketServerProtocol` constructor + + :param client_subprotocols: list of subprotocols offered by the client + :param server_subprotocols: list of subprotocols available on the server + + """ + if self._select_subprotocol is not None: + return self._select_subprotocol(client_subprotocols, server_subprotocols) + + subprotocols = set(client_subprotocols) & set(server_subprotocols) + if not subprotocols: + return None + priority = lambda p: ( + client_subprotocols.index(p) + server_subprotocols.index(p) + ) + return sorted(subprotocols, key=priority)[0] + + async def handshake( + self, + origins: Optional[Sequence[Optional[Origin]]] = None, + available_extensions: Optional[Sequence[ServerExtensionFactory]] = None, + available_subprotocols: Optional[Sequence[Subprotocol]] = None, + extra_headers: Optional[HeadersLikeOrCallable] = None, + ) -> str: + """ + Perform the server side of the opening handshake. + + Return the path of the URI of the request. + + :param origins: list of acceptable values of the Origin HTTP header; + include ``None`` if the lack of an origin is acceptable + :param available_extensions: list of supported extensions in the order + in which they should be used + :param available_subprotocols: list of supported subprotocols in order + of decreasing preference + :param extra_headers: sets additional HTTP response headers when the + handshake succeeds; it can be a :class:`~websockets.http.Headers` + instance, a :class:`~collections.abc.Mapping`, an iterable of + ``(name, value)`` pairs, or a callable taking the request path and + headers in arguments and returning one of the above. + :raises ~websockets.exceptions.InvalidHandshake: if the handshake + fails + + """ + path, request_headers = await self.read_http_request() + + # Hook for customizing request handling, for example checking + # authentication or treating some paths as plain HTTP endpoints. + early_response_awaitable = self.process_request(path, request_headers) + if isinstance(early_response_awaitable, Awaitable): + early_response = await early_response_awaitable + else: + # For backwards compatibility with 7.0. + warnings.warn("declare process_request as a coroutine", DeprecationWarning) + early_response = early_response_awaitable # type: ignore + + # Change the response to a 503 error if the server is shutting down. + if not self.ws_server.is_serving(): + early_response = ( + http.HTTPStatus.SERVICE_UNAVAILABLE, + [], + b"Server is shutting down.\n", + ) + + if early_response is not None: + raise AbortHandshake(*early_response) + + key = check_request(request_headers) + + self.origin = self.process_origin(request_headers, origins) + + extensions_header, self.extensions = self.process_extensions( + request_headers, available_extensions + ) + + protocol_header = self.subprotocol = self.process_subprotocol( + request_headers, available_subprotocols + ) + + response_headers = Headers() + + build_response(response_headers, key) + + if extensions_header is not None: + response_headers["Sec-WebSocket-Extensions"] = extensions_header + + if protocol_header is not None: + response_headers["Sec-WebSocket-Protocol"] = protocol_header + + if callable(extra_headers): + extra_headers = extra_headers(path, self.request_headers) + if extra_headers is not None: + if isinstance(extra_headers, Headers): + extra_headers = extra_headers.raw_items() + elif isinstance(extra_headers, collections.abc.Mapping): + extra_headers = extra_headers.items() + for name, value in extra_headers: + response_headers[name] = value + + response_headers.setdefault("Date", email.utils.formatdate(usegmt=True)) + response_headers.setdefault("Server", USER_AGENT) + + self.write_http_response(http.HTTPStatus.SWITCHING_PROTOCOLS, response_headers) + + self.connection_open() + + return path + + +class WebSocketServer: + """ + WebSocket server returned by :func:`~websockets.server.serve`. + + This class provides the same interface as + :class:`~asyncio.AbstractServer`, namely the + :meth:`~asyncio.AbstractServer.close` and + :meth:`~asyncio.AbstractServer.wait_closed` methods. + + It keeps track of WebSocket connections in order to close them properly + when shutting down. + + Instances of this class store a reference to the :class:`~asyncio.Server` + object returned by :meth:`~asyncio.loop.create_server` rather than inherit + from :class:`~asyncio.Server` in part because + :meth:`~asyncio.loop.create_server` doesn't support passing a custom + :class:`~asyncio.Server` class. + + """ + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + # Store a reference to loop to avoid relying on self.server._loop. + self.loop = loop + + # Keep track of active connections. + self.websockets: Set[WebSocketServerProtocol] = set() + + # Task responsible for closing the server and terminating connections. + self.close_task: Optional[asyncio.Task[None]] = None + + # Completed when the server is closed and connections are terminated. + self.closed_waiter: asyncio.Future[None] = loop.create_future() + + def wrap(self, server: asyncio.AbstractServer) -> None: + """ + Attach to a given :class:`~asyncio.Server`. + + Since :meth:`~asyncio.loop.create_server` doesn't support injecting a + custom ``Server`` class, the easiest solution that doesn't rely on + private :mod:`asyncio` APIs is to: + + - instantiate a :class:`WebSocketServer` + - give the protocol factory a reference to that instance + - call :meth:`~asyncio.loop.create_server` with the factory + - attach the resulting :class:`~asyncio.Server` with this method + + """ + self.server = server + + def register(self, protocol: WebSocketServerProtocol) -> None: + """ + Register a connection with this server. + + """ + self.websockets.add(protocol) + + def unregister(self, protocol: WebSocketServerProtocol) -> None: + """ + Unregister a connection with this server. + + """ + self.websockets.remove(protocol) + + def is_serving(self) -> bool: + """ + Tell whether the server is accepting new connections or shutting down. + + """ + try: + # Python ≥ 3.7 + return self.server.is_serving() + except AttributeError: # pragma: no cover + # Python < 3.7 + return self.server.sockets is not None + + def close(self) -> None: + """ + Close the server. + + This method: + + * closes the underlying :class:`~asyncio.Server`; + * rejects new WebSocket connections with an HTTP 503 (service + unavailable) error; this happens when the server accepted the TCP + connection but didn't complete the WebSocket opening handshake prior + to closing; + * closes open WebSocket connections with close code 1001 (going away). + + :meth:`close` is idempotent. + + """ + if self.close_task is None: + self.close_task = self.loop.create_task(self._close()) + + async def _close(self) -> None: + """ + Implementation of :meth:`close`. + + This calls :meth:`~asyncio.Server.close` on the underlying + :class:`~asyncio.Server` object to stop accepting new connections and + then closes open connections with close code 1001. + + """ + # Stop accepting new connections. + self.server.close() + + # Wait until self.server.close() completes. + await self.server.wait_closed() + + # Wait until all accepted connections reach connection_made() and call + # register(). See https://bugs.python.org/issue34852 for details. + await asyncio.sleep( + 0, loop=self.loop if sys.version_info[:2] < (3, 8) else None + ) + + # Close OPEN connections with status code 1001. Since the server was + # closed, handshake() closes OPENING conections with a HTTP 503 error. + # Wait until all connections are closed. + + # asyncio.wait doesn't accept an empty first argument + if self.websockets: + await asyncio.wait( + [websocket.close(1001) for websocket in self.websockets], + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + + # Wait until all connection handlers are complete. + + # asyncio.wait doesn't accept an empty first argument. + if self.websockets: + await asyncio.wait( + [websocket.handler_task for websocket in self.websockets], + loop=self.loop if sys.version_info[:2] < (3, 8) else None, + ) + + # Tell wait_closed() to return. + self.closed_waiter.set_result(None) + + async def wait_closed(self) -> None: + """ + Wait until the server is closed. + + When :meth:`wait_closed` returns, all TCP connections are closed and + all connection handlers have returned. + + """ + await asyncio.shield(self.closed_waiter) + + @property + def sockets(self) -> Optional[List[socket.socket]]: + """ + List of :class:`~socket.socket` objects the server is listening to. + + ``None`` if the server is closed. + + """ + return self.server.sockets + + +class Serve: + """ + + Create, start, and return a WebSocket server on ``host`` and ``port``. + + Whenever a client connects, the server accepts the connection, creates a + :class:`WebSocketServerProtocol`, performs the opening handshake, and + delegates to the connection handler defined by ``ws_handler``. Once the + handler completes, either normally or with an exception, the server + performs the closing handshake and closes the connection. + + Awaiting :func:`serve` yields a :class:`WebSocketServer`. This instance + provides :meth:`~websockets.server.WebSocketServer.close` and + :meth:`~websockets.server.WebSocketServer.wait_closed` methods for + terminating the server and cleaning up its resources. + + When a server is closed with :meth:`~WebSocketServer.close`, it closes all + connections with close code 1001 (going away). Connections handlers, which + are running the ``ws_handler`` coroutine, will receive a + :exc:`~websockets.exceptions.ConnectionClosedOK` exception on their + current or next interaction with the WebSocket connection. + + :func:`serve` can also be used as an asynchronous context manager. In + this case, the server is shut down when exiting the context. + + :func:`serve` is a wrapper around the event loop's + :meth:`~asyncio.loop.create_server` method. It creates and starts a + :class:`~asyncio.Server` with :meth:`~asyncio.loop.create_server`. Then it + wraps the :class:`~asyncio.Server` in a :class:`WebSocketServer` and + returns the :class:`WebSocketServer`. + + The ``ws_handler`` argument is the WebSocket handler. It must be a + coroutine accepting two arguments: a :class:`WebSocketServerProtocol` and + the request URI. + + The ``host`` and ``port`` arguments, as well as unrecognized keyword + arguments, are passed along to :meth:`~asyncio.loop.create_server`. + + For example, you can set the ``ssl`` keyword argument to a + :class:`~ssl.SSLContext` to enable TLS. + + The ``create_protocol`` parameter allows customizing the + :class:`~asyncio.Protocol` that manages the connection. It should be a + callable or class accepting the same arguments as + :class:`WebSocketServerProtocol` and returning an instance of + :class:`WebSocketServerProtocol` or a subclass. It defaults to + :class:`WebSocketServerProtocol`. + + The behavior of ``ping_interval``, ``ping_timeout``, ``close_timeout``, + ``max_size``, ``max_queue``, ``read_limit``, and ``write_limit`` is + described in :class:`~websockets.protocol.WebSocketCommonProtocol`. + + :func:`serve` also accepts the following optional arguments: + + * ``compression`` is a shortcut to configure compression extensions; + by default it enables the "permessage-deflate" extension; set it to + ``None`` to disable compression + * ``origins`` defines acceptable Origin HTTP headers; include ``None`` if + the lack of an origin is acceptable + * ``extensions`` is a list of supported extensions in order of + decreasing preference + * ``subprotocols`` is a list of supported subprotocols in order of + decreasing preference + * ``extra_headers`` sets additional HTTP response headers when the + handshake succeeds; it can be a :class:`~websockets.http.Headers` + instance, a :class:`~collections.abc.Mapping`, an iterable of ``(name, + value)`` pairs, or a callable taking the request path and headers in + arguments and returning one of the above + * ``process_request`` allows intercepting the HTTP request; it must be a + coroutine taking the request path and headers in argument; see + :meth:`~WebSocketServerProtocol.process_request` for details + * ``select_subprotocol`` allows customizing the logic for selecting a + subprotocol; it must be a callable taking the subprotocols offered by + the client and available on the server in argument; see + :meth:`~WebSocketServerProtocol.select_subprotocol` for details + + Since there's no useful way to propagate exceptions triggered in handlers, + they're sent to the ``'websockets.server'`` logger instead. Debugging is + much easier if you configure logging to print them:: + + import logging + logger = logging.getLogger('websockets.server') + logger.setLevel(logging.ERROR) + logger.addHandler(logging.StreamHandler()) + + """ + + def __init__( + self, + ws_handler: Callable[[WebSocketServerProtocol, str], Awaitable[Any]], + host: Optional[Union[str, Sequence[str]]] = None, + port: Optional[int] = None, + *, + path: Optional[str] = None, + create_protocol: Optional[Type[WebSocketServerProtocol]] = None, + ping_interval: float = 20, + ping_timeout: float = 20, + close_timeout: Optional[float] = None, + max_size: int = 2 ** 20, + max_queue: int = 2 ** 5, + read_limit: int = 2 ** 16, + write_limit: int = 2 ** 16, + loop: Optional[asyncio.AbstractEventLoop] = None, + legacy_recv: bool = False, + klass: Optional[Type[WebSocketServerProtocol]] = None, + timeout: Optional[float] = None, + compression: Optional[str] = "deflate", + origins: Optional[Sequence[Optional[Origin]]] = None, + extensions: Optional[Sequence[ServerExtensionFactory]] = None, + subprotocols: Optional[Sequence[Subprotocol]] = None, + extra_headers: Optional[HeadersLikeOrCallable] = None, + process_request: Optional[ + Callable[[str, Headers], Awaitable[Optional[HTTPResponse]]] + ] = None, + select_subprotocol: Optional[ + Callable[[Sequence[Subprotocol], Sequence[Subprotocol]], Subprotocol] + ] = None, + **kwargs: Any, + ) -> None: + # Backwards compatibility: close_timeout used to be called timeout. + if timeout is None: + timeout = 10 + else: + warnings.warn("rename timeout to close_timeout", DeprecationWarning) + # If both are specified, timeout is ignored. + if close_timeout is None: + close_timeout = timeout + + # Backwards compatibility: create_protocol used to be called klass. + if klass is None: + klass = WebSocketServerProtocol + else: + warnings.warn("rename klass to create_protocol", DeprecationWarning) + # If both are specified, klass is ignored. + if create_protocol is None: + create_protocol = klass + + if loop is None: + loop = asyncio.get_event_loop() + + ws_server = WebSocketServer(loop) + + secure = kwargs.get("ssl") is not None + + if compression == "deflate": + if extensions is None: + extensions = [] + if not any( + ext_factory.name == ServerPerMessageDeflateFactory.name + for ext_factory in extensions + ): + extensions = list(extensions) + [ServerPerMessageDeflateFactory()] + elif compression is not None: + raise ValueError(f"unsupported compression: {compression}") + + factory = functools.partial( + create_protocol, + ws_handler, + ws_server, + host=host, + port=port, + secure=secure, + ping_interval=ping_interval, + ping_timeout=ping_timeout, + close_timeout=close_timeout, + max_size=max_size, + max_queue=max_queue, + read_limit=read_limit, + write_limit=write_limit, + loop=loop, + legacy_recv=legacy_recv, + origins=origins, + extensions=extensions, + subprotocols=subprotocols, + extra_headers=extra_headers, + process_request=process_request, + select_subprotocol=select_subprotocol, + ) + + if path is None: + create_server = functools.partial( + loop.create_server, factory, host, port, **kwargs + ) + else: + # unix_serve(path) must not specify host and port parameters. + assert host is None and port is None + create_server = functools.partial( + loop.create_unix_server, factory, path, **kwargs + ) + + # This is a coroutine function. + self._create_server = create_server + self.ws_server = ws_server + + # async with serve(...) + + async def __aenter__(self) -> WebSocketServer: + return await self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + self.ws_server.close() + await self.ws_server.wait_closed() + + # await serve(...) + + def __await__(self) -> Generator[Any, None, WebSocketServer]: + # Create a suitable iterator by calling __await__ on a coroutine. + return self.__await_impl__().__await__() + + async def __await_impl__(self) -> WebSocketServer: + server = await self._create_server() + self.ws_server.wrap(server) + return self.ws_server + + # yield from serve(...) + + __iter__ = __await__ + + +serve = Serve + + +def unix_serve( + ws_handler: Callable[[WebSocketServerProtocol, str], Awaitable[Any]], + path: str, + **kwargs: Any, +) -> Serve: + """ + Similar to :func:`serve`, but for listening on Unix sockets. + + This function calls the event loop's + :meth:`~asyncio.loop.create_unix_server` method. + + It is only available on Unix. + + It's useful for deploying a server behind a reverse proxy such as nginx. + + :param path: file system path to the Unix socket + + """ + return serve(ws_handler, path=path, **kwargs) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.c b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.c new file mode 100644 index 00000000000..d1c2b37e60c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.c @@ -0,0 +1,206 @@ +/* C implementation of performance sensitive functions. */ + +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include <stdint.h> /* uint32_t, uint64_t */ + +#if __SSE2__ +#include <emmintrin.h> +#endif + +static const Py_ssize_t MASK_LEN = 4; + +/* Similar to PyBytes_AsStringAndSize, but accepts more types */ + +static int +_PyBytesLike_AsStringAndSize(PyObject *obj, char **buffer, Py_ssize_t *length) +{ + // This supports bytes, bytearrays, and C-contiguous memoryview objects, + // which are the most useful data structures for handling byte streams. + // websockets.framing.prepare_data() returns only values of these types. + // Any object implementing the buffer protocol could be supported, however + // that would require allocation or copying memory, which is expensive. + if (PyBytes_Check(obj)) + { + *buffer = PyBytes_AS_STRING(obj); + *length = PyBytes_GET_SIZE(obj); + } + else if (PyByteArray_Check(obj)) + { + *buffer = PyByteArray_AS_STRING(obj); + *length = PyByteArray_GET_SIZE(obj); + } + else if (PyMemoryView_Check(obj)) + { + Py_buffer *mv_buf; + mv_buf = PyMemoryView_GET_BUFFER(obj); + if (PyBuffer_IsContiguous(mv_buf, 'C')) + { + *buffer = mv_buf->buf; + *length = mv_buf->len; + } + else + { + PyErr_Format( + PyExc_TypeError, + "expected a contiguous memoryview"); + return -1; + } + } + else + { + PyErr_Format( + PyExc_TypeError, + "expected a bytes-like object, %.200s found", + Py_TYPE(obj)->tp_name); + return -1; + } + + return 0; +} + +/* C implementation of websockets.utils.apply_mask */ + +static PyObject * +apply_mask(PyObject *self, PyObject *args, PyObject *kwds) +{ + + // In order to support various bytes-like types, accept any Python object. + + static char *kwlist[] = {"data", "mask", NULL}; + PyObject *input_obj; + PyObject *mask_obj; + + // A pointer to a char * + length will be extracted from the data and mask + // arguments, possibly via a Py_buffer. + + char *input; + Py_ssize_t input_len; + char *mask; + Py_ssize_t mask_len; + + // Initialize a PyBytesObject then get a pointer to the underlying char * + // in order to avoid an extra memory copy in PyBytes_FromStringAndSize. + + PyObject *result; + char *output; + + // Other variables. + + Py_ssize_t i = 0; + + // Parse inputs. + + if (!PyArg_ParseTupleAndKeywords( + args, kwds, "OO", kwlist, &input_obj, &mask_obj)) + { + return NULL; + } + + if (_PyBytesLike_AsStringAndSize(input_obj, &input, &input_len) == -1) + { + return NULL; + } + + if (_PyBytesLike_AsStringAndSize(mask_obj, &mask, &mask_len) == -1) + { + return NULL; + } + + if (mask_len != MASK_LEN) + { + PyErr_SetString(PyExc_ValueError, "mask must contain 4 bytes"); + return NULL; + } + + // Create output. + + result = PyBytes_FromStringAndSize(NULL, input_len); + if (result == NULL) + { + return NULL; + } + + // Since we juste created result, we don't need error checks. + output = PyBytes_AS_STRING(result); + + // Perform the masking operation. + + // Apparently GCC cannot figure out the following optimizations by itself. + + // We need a new scope for MSVC 2010 (non C99 friendly) + { +#if __SSE2__ + + // With SSE2 support, XOR by blocks of 16 bytes = 128 bits. + + // Since we cannot control the 16-bytes alignment of input and output + // buffers, we rely on loadu/storeu rather than load/store. + + Py_ssize_t input_len_128 = input_len & ~15; + __m128i mask_128 = _mm_set1_epi32(*(uint32_t *)mask); + + for (; i < input_len_128; i += 16) + { + __m128i in_128 = _mm_loadu_si128((__m128i *)(input + i)); + __m128i out_128 = _mm_xor_si128(in_128, mask_128); + _mm_storeu_si128((__m128i *)(output + i), out_128); + } + +#else + + // Without SSE2 support, XOR by blocks of 8 bytes = 64 bits. + + // We assume the memory allocator aligns everything on 8 bytes boundaries. + + Py_ssize_t input_len_64 = input_len & ~7; + uint32_t mask_32 = *(uint32_t *)mask; + uint64_t mask_64 = ((uint64_t)mask_32 << 32) | (uint64_t)mask_32; + + for (; i < input_len_64; i += 8) + { + *(uint64_t *)(output + i) = *(uint64_t *)(input + i) ^ mask_64; + } + +#endif + } + + // XOR the remainder of the input byte by byte. + + for (; i < input_len; i++) + { + output[i] = input[i] ^ mask[i & (MASK_LEN - 1)]; + } + + return result; + +} + +static PyMethodDef speedups_methods[] = { + { + "apply_mask", + (PyCFunction)apply_mask, + METH_VARARGS | METH_KEYWORDS, + "Apply masking to websocket message.", + }, + {NULL, NULL, 0, NULL}, /* Sentinel */ +}; + +static struct PyModuleDef speedups_module = { + PyModuleDef_HEAD_INIT, + "websocket.speedups", /* m_name */ + "C implementation of performance sensitive functions.", + /* m_doc */ + -1, /* m_size */ + speedups_methods, /* m_methods */ + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit_speedups(void) +{ + return PyModule_Create(&speedups_module); +} diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.pyi b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.pyi new file mode 100644 index 00000000000..821438a064e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/speedups.pyi @@ -0,0 +1 @@ +def apply_mask(data: bytes, mask: bytes) -> bytes: ... diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py new file mode 100644 index 00000000000..4a60f93f64b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/typing.py @@ -0,0 +1,49 @@ +from typing import List, NewType, Optional, Tuple, Union + + +__all__ = ["Data", "Origin", "ExtensionHeader", "ExtensionParameter", "Subprotocol"] + +Data = Union[str, bytes] + +Data__doc__ = """ +Types supported in a WebSocket message: + +- :class:`str` for text messages +- :class:`bytes` for binary messages + +""" +# Remove try / except when dropping support for Python < 3.7 +try: + Data.__doc__ = Data__doc__ # type: ignore +except AttributeError: # pragma: no cover + pass + + +Origin = NewType("Origin", str) +Origin.__doc__ = """Value of a Origin header""" + + +ExtensionName = NewType("ExtensionName", str) +ExtensionName.__doc__ = """Name of a WebSocket extension""" + + +ExtensionParameter = Tuple[str, Optional[str]] + +ExtensionParameter__doc__ = """Parameter of a WebSocket extension""" +try: + ExtensionParameter.__doc__ = ExtensionParameter__doc__ # type: ignore +except AttributeError: # pragma: no cover + pass + + +ExtensionHeader = Tuple[ExtensionName, List[ExtensionParameter]] + +ExtensionHeader__doc__ = """Item parsed in a Sec-WebSocket-Extensions header""" +try: + ExtensionHeader.__doc__ = ExtensionHeader__doc__ # type: ignore +except AttributeError: # pragma: no cover + pass + + +Subprotocol = NewType("Subprotocol", str) +Subprotocol.__doc__ = """Items parsed in a Sec-WebSocket-Protocol header""" diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py new file mode 100644 index 00000000000..6669e566861 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/uri.py @@ -0,0 +1,81 @@ +""" +:mod:`websockets.uri` parses WebSocket URIs. + +See `section 3 of RFC 6455`_. + +.. _section 3 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-3 + +""" + +import urllib.parse +from typing import NamedTuple, Optional, Tuple + +from .exceptions import InvalidURI + + +__all__ = ["parse_uri", "WebSocketURI"] + + +# Consider converting to a dataclass when dropping support for Python < 3.7. + + +class WebSocketURI(NamedTuple): + """ + WebSocket URI. + + :param bool secure: secure flag + :param str host: lower-case host + :param int port: port, always set even if it's the default + :param str resource_name: path and optional query + :param str user_info: ``(username, password)`` tuple when the URI contains + `User Information`_, else ``None``. + + .. _User Information: https://tools.ietf.org/html/rfc3986#section-3.2.1 + """ + + secure: bool + host: str + port: int + resource_name: str + user_info: Optional[Tuple[str, str]] + + +# Work around https://bugs.python.org/issue19931 + +WebSocketURI.secure.__doc__ = "" +WebSocketURI.host.__doc__ = "" +WebSocketURI.port.__doc__ = "" +WebSocketURI.resource_name.__doc__ = "" +WebSocketURI.user_info.__doc__ = "" + + +def parse_uri(uri: str) -> WebSocketURI: + """ + Parse and validate a WebSocket URI. + + :raises ValueError: if ``uri`` isn't a valid WebSocket URI. + + """ + parsed = urllib.parse.urlparse(uri) + try: + assert parsed.scheme in ["ws", "wss"] + assert parsed.params == "" + assert parsed.fragment == "" + assert parsed.hostname is not None + except AssertionError as exc: + raise InvalidURI(uri) from exc + + secure = parsed.scheme == "wss" + host = parsed.hostname + port = parsed.port or (443 if secure else 80) + resource_name = parsed.path or "/" + if parsed.query: + resource_name += "?" + parsed.query + user_info = None + if parsed.username is not None: + # urllib.parse.urlparse accepts URLs with a username but without a + # password. This doesn't make sense for HTTP Basic Auth credentials. + if parsed.password is None: + raise InvalidURI(uri) + user_info = (parsed.username, parsed.password) + return WebSocketURI(secure, host, port, resource_name, user_info) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/utils.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/utils.py new file mode 100644 index 00000000000..40ac8559ff6 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/utils.py @@ -0,0 +1,18 @@ +import itertools + + +__all__ = ["apply_mask"] + + +def apply_mask(data: bytes, mask: bytes) -> bytes: + """ + Apply masking to the data of a WebSocket message. + + :param data: Data to mask + :param mask: 4-bytes mask + + """ + if len(mask) != 4: + raise ValueError("mask must contain 4 bytes") + + return bytes(b ^ m for b, m in zip(data, itertools.cycle(mask))) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/version.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/version.py new file mode 100644 index 00000000000..7377332e12e --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/src/websockets/version.py @@ -0,0 +1 @@ +version = "8.1" diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/__init__.py new file mode 100644 index 00000000000..dd78609f5ba --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/__init__.py @@ -0,0 +1,5 @@ +import logging + + +# Avoid displaying stack traces at the ERROR logging level. +logging.basicConfig(level=logging.CRITICAL) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/__init__.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/__init__.py diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/test_base.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/test_base.py new file mode 100644 index 00000000000..ba8657b6541 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/test_base.py @@ -0,0 +1,4 @@ +from websockets.extensions.base import * # noqa + + +# Abstract classes don't provide any behavior to test. diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py new file mode 100644 index 00000000000..0ec49c6c02d --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py @@ -0,0 +1,792 @@ +import unittest +import zlib + +from websockets.exceptions import ( + DuplicateParameter, + InvalidParameterName, + InvalidParameterValue, + NegotiationError, + PayloadTooBig, +) +from websockets.extensions.permessage_deflate import * +from websockets.framing import ( + OP_BINARY, + OP_CLOSE, + OP_CONT, + OP_PING, + OP_PONG, + OP_TEXT, + Frame, + serialize_close, +) + + +class ExtensionTestsMixin: + def assertExtensionEqual(self, extension1, extension2): + self.assertEqual( + extension1.remote_no_context_takeover, extension2.remote_no_context_takeover + ) + self.assertEqual( + extension1.local_no_context_takeover, extension2.local_no_context_takeover + ) + self.assertEqual( + extension1.remote_max_window_bits, extension2.remote_max_window_bits + ) + self.assertEqual( + extension1.local_max_window_bits, extension2.local_max_window_bits + ) + + +class PerMessageDeflateTests(unittest.TestCase, ExtensionTestsMixin): + def setUp(self): + # Set up an instance of the permessage-deflate extension with the most + # common settings. Since the extension is symmetrical, this instance + # may be used for testing both encoding and decoding. + self.extension = PerMessageDeflate(False, False, 15, 15) + + def test_name(self): + assert self.extension.name == "permessage-deflate" + + def test_repr(self): + self.assertExtensionEqual(eval(repr(self.extension)), self.extension) + + # Control frames aren't encoded or decoded. + + def test_no_encode_decode_ping_frame(self): + frame = Frame(True, OP_PING, b"") + + self.assertEqual(self.extension.encode(frame), frame) + + self.assertEqual(self.extension.decode(frame), frame) + + def test_no_encode_decode_pong_frame(self): + frame = Frame(True, OP_PONG, b"") + + self.assertEqual(self.extension.encode(frame), frame) + + self.assertEqual(self.extension.decode(frame), frame) + + def test_no_encode_decode_close_frame(self): + frame = Frame(True, OP_CLOSE, serialize_close(1000, "")) + + self.assertEqual(self.extension.encode(frame), frame) + + self.assertEqual(self.extension.decode(frame), frame) + + # Data frames are encoded and decoded. + + def test_encode_decode_text_frame(self): + frame = Frame(True, OP_TEXT, "café".encode("utf-8")) + + enc_frame = self.extension.encode(frame) + + self.assertEqual(enc_frame, frame._replace(rsv1=True, data=b"JNL;\xbc\x12\x00")) + + dec_frame = self.extension.decode(enc_frame) + + self.assertEqual(dec_frame, frame) + + def test_encode_decode_binary_frame(self): + frame = Frame(True, OP_BINARY, b"tea") + + enc_frame = self.extension.encode(frame) + + self.assertEqual(enc_frame, frame._replace(rsv1=True, data=b"*IM\x04\x00")) + + dec_frame = self.extension.decode(enc_frame) + + self.assertEqual(dec_frame, frame) + + def test_encode_decode_fragmented_text_frame(self): + frame1 = Frame(False, OP_TEXT, "café".encode("utf-8")) + frame2 = Frame(False, OP_CONT, " & ".encode("utf-8")) + frame3 = Frame(True, OP_CONT, "croissants".encode("utf-8")) + + enc_frame1 = self.extension.encode(frame1) + enc_frame2 = self.extension.encode(frame2) + enc_frame3 = self.extension.encode(frame3) + + self.assertEqual( + enc_frame1, + frame1._replace(rsv1=True, data=b"JNL;\xbc\x12\x00\x00\x00\xff\xff"), + ) + self.assertEqual( + enc_frame2, frame2._replace(rsv1=True, data=b"RPS\x00\x00\x00\x00\xff\xff") + ) + self.assertEqual( + enc_frame3, frame3._replace(rsv1=True, data=b"J.\xca\xcf,.N\xcc+)\x06\x00") + ) + + dec_frame1 = self.extension.decode(enc_frame1) + dec_frame2 = self.extension.decode(enc_frame2) + dec_frame3 = self.extension.decode(enc_frame3) + + self.assertEqual(dec_frame1, frame1) + self.assertEqual(dec_frame2, frame2) + self.assertEqual(dec_frame3, frame3) + + def test_encode_decode_fragmented_binary_frame(self): + frame1 = Frame(False, OP_TEXT, b"tea ") + frame2 = Frame(True, OP_CONT, b"time") + + enc_frame1 = self.extension.encode(frame1) + enc_frame2 = self.extension.encode(frame2) + + self.assertEqual( + enc_frame1, frame1._replace(rsv1=True, data=b"*IMT\x00\x00\x00\x00\xff\xff") + ) + self.assertEqual( + enc_frame2, frame2._replace(rsv1=True, data=b"*\xc9\xccM\x05\x00") + ) + + dec_frame1 = self.extension.decode(enc_frame1) + dec_frame2 = self.extension.decode(enc_frame2) + + self.assertEqual(dec_frame1, frame1) + self.assertEqual(dec_frame2, frame2) + + def test_no_decode_text_frame(self): + frame = Frame(True, OP_TEXT, "café".encode("utf-8")) + + # Try decoding a frame that wasn't encoded. + self.assertEqual(self.extension.decode(frame), frame) + + def test_no_decode_binary_frame(self): + frame = Frame(True, OP_TEXT, b"tea") + + # Try decoding a frame that wasn't encoded. + self.assertEqual(self.extension.decode(frame), frame) + + def test_no_decode_fragmented_text_frame(self): + frame1 = Frame(False, OP_TEXT, "café".encode("utf-8")) + frame2 = Frame(False, OP_CONT, " & ".encode("utf-8")) + frame3 = Frame(True, OP_CONT, "croissants".encode("utf-8")) + + dec_frame1 = self.extension.decode(frame1) + dec_frame2 = self.extension.decode(frame2) + dec_frame3 = self.extension.decode(frame3) + + self.assertEqual(dec_frame1, frame1) + self.assertEqual(dec_frame2, frame2) + self.assertEqual(dec_frame3, frame3) + + def test_no_decode_fragmented_binary_frame(self): + frame1 = Frame(False, OP_TEXT, b"tea ") + frame2 = Frame(True, OP_CONT, b"time") + + dec_frame1 = self.extension.decode(frame1) + dec_frame2 = self.extension.decode(frame2) + + self.assertEqual(dec_frame1, frame1) + self.assertEqual(dec_frame2, frame2) + + def test_context_takeover(self): + frame = Frame(True, OP_TEXT, "café".encode("utf-8")) + + enc_frame1 = self.extension.encode(frame) + enc_frame2 = self.extension.encode(frame) + + self.assertEqual(enc_frame1.data, b"JNL;\xbc\x12\x00") + self.assertEqual(enc_frame2.data, b"J\x06\x11\x00\x00") + + def test_remote_no_context_takeover(self): + # No context takeover when decoding messages. + self.extension = PerMessageDeflate(True, False, 15, 15) + + frame = Frame(True, OP_TEXT, "café".encode("utf-8")) + + enc_frame1 = self.extension.encode(frame) + enc_frame2 = self.extension.encode(frame) + + self.assertEqual(enc_frame1.data, b"JNL;\xbc\x12\x00") + self.assertEqual(enc_frame2.data, b"J\x06\x11\x00\x00") + + dec_frame1 = self.extension.decode(enc_frame1) + self.assertEqual(dec_frame1, frame) + + with self.assertRaises(zlib.error) as exc: + self.extension.decode(enc_frame2) + self.assertIn("invalid distance too far back", str(exc.exception)) + + def test_local_no_context_takeover(self): + # No context takeover when encoding and decoding messages. + self.extension = PerMessageDeflate(True, True, 15, 15) + + frame = Frame(True, OP_TEXT, "café".encode("utf-8")) + + enc_frame1 = self.extension.encode(frame) + enc_frame2 = self.extension.encode(frame) + + self.assertEqual(enc_frame1.data, b"JNL;\xbc\x12\x00") + self.assertEqual(enc_frame2.data, b"JNL;\xbc\x12\x00") + + dec_frame1 = self.extension.decode(enc_frame1) + dec_frame2 = self.extension.decode(enc_frame2) + + self.assertEqual(dec_frame1, frame) + self.assertEqual(dec_frame2, frame) + + # Compression settings can be customized. + + def test_compress_settings(self): + # Configure an extension so that no compression actually occurs. + extension = PerMessageDeflate(False, False, 15, 15, {"level": 0}) + + frame = Frame(True, OP_TEXT, "café".encode("utf-8")) + + enc_frame = extension.encode(frame) + + self.assertEqual( + enc_frame, + frame._replace( + rsv1=True, data=b"\x00\x05\x00\xfa\xffcaf\xc3\xa9\x00" # not compressed + ), + ) + + # Frames aren't decoded beyond max_length. + + def test_decompress_max_size(self): + frame = Frame(True, OP_TEXT, ("a" * 20).encode("utf-8")) + + enc_frame = self.extension.encode(frame) + + self.assertEqual(enc_frame.data, b"JL\xc4\x04\x00\x00") + + with self.assertRaises(PayloadTooBig): + self.extension.decode(enc_frame, max_size=10) + + +class ClientPerMessageDeflateFactoryTests(unittest.TestCase, ExtensionTestsMixin): + def test_name(self): + assert ClientPerMessageDeflateFactory.name == "permessage-deflate" + + def test_init(self): + for config in [ + (False, False, 8, None), # server_max_window_bits ≥ 8 + (False, True, 15, None), # server_max_window_bits ≤ 15 + (True, False, None, 8), # client_max_window_bits ≥ 8 + (True, True, None, 15), # client_max_window_bits ≤ 15 + (False, False, None, True), # client_max_window_bits + (False, False, None, None, {"memLevel": 4}), + ]: + with self.subTest(config=config): + # This does not raise an exception. + ClientPerMessageDeflateFactory(*config) + + def test_init_error(self): + for config in [ + (False, False, 7, 8), # server_max_window_bits < 8 + (False, True, 8, 7), # client_max_window_bits < 8 + (True, False, 16, 15), # server_max_window_bits > 15 + (True, True, 15, 16), # client_max_window_bits > 15 + (False, False, True, None), # server_max_window_bits + (False, False, None, None, {"wbits": 11}), + ]: + with self.subTest(config=config): + with self.assertRaises(ValueError): + ClientPerMessageDeflateFactory(*config) + + def test_get_request_params(self): + for config, result in [ + # Test without any parameter + ((False, False, None, None), []), + # Test server_no_context_takeover + ((True, False, None, None), [("server_no_context_takeover", None)]), + # Test client_no_context_takeover + ((False, True, None, None), [("client_no_context_takeover", None)]), + # Test server_max_window_bits + ((False, False, 10, None), [("server_max_window_bits", "10")]), + # Test client_max_window_bits + ((False, False, None, 10), [("client_max_window_bits", "10")]), + ((False, False, None, True), [("client_max_window_bits", None)]), + # Test all parameters together + ( + (True, True, 12, 12), + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "12"), + ("client_max_window_bits", "12"), + ], + ), + ]: + with self.subTest(config=config): + factory = ClientPerMessageDeflateFactory(*config) + self.assertEqual(factory.get_request_params(), result) + + def test_process_response_params(self): + for config, response_params, result in [ + # Test without any parameter + ((False, False, None, None), [], (False, False, 15, 15)), + ((False, False, None, None), [("unknown", None)], InvalidParameterName), + # Test server_no_context_takeover + ( + (False, False, None, None), + [("server_no_context_takeover", None)], + (True, False, 15, 15), + ), + ((True, False, None, None), [], NegotiationError), + ( + (True, False, None, None), + [("server_no_context_takeover", None)], + (True, False, 15, 15), + ), + ( + (True, False, None, None), + [("server_no_context_takeover", None)] * 2, + DuplicateParameter, + ), + ( + (True, False, None, None), + [("server_no_context_takeover", "42")], + InvalidParameterValue, + ), + # Test client_no_context_takeover + ( + (False, False, None, None), + [("client_no_context_takeover", None)], + (False, True, 15, 15), + ), + ((False, True, None, None), [], (False, True, 15, 15)), + ( + (False, True, None, None), + [("client_no_context_takeover", None)], + (False, True, 15, 15), + ), + ( + (False, True, None, None), + [("client_no_context_takeover", None)] * 2, + DuplicateParameter, + ), + ( + (False, True, None, None), + [("client_no_context_takeover", "42")], + InvalidParameterValue, + ), + # Test server_max_window_bits + ( + (False, False, None, None), + [("server_max_window_bits", "7")], + NegotiationError, + ), + ( + (False, False, None, None), + [("server_max_window_bits", "10")], + (False, False, 10, 15), + ), + ( + (False, False, None, None), + [("server_max_window_bits", "16")], + NegotiationError, + ), + ((False, False, 12, None), [], NegotiationError), + ( + (False, False, 12, None), + [("server_max_window_bits", "10")], + (False, False, 10, 15), + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "12")], + (False, False, 12, 15), + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "13")], + NegotiationError, + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "12")] * 2, + DuplicateParameter, + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "42")], + InvalidParameterValue, + ), + # Test client_max_window_bits + ( + (False, False, None, None), + [("client_max_window_bits", "10")], + NegotiationError, + ), + ((False, False, None, True), [], (False, False, 15, 15)), + ( + (False, False, None, True), + [("client_max_window_bits", "7")], + NegotiationError, + ), + ( + (False, False, None, True), + [("client_max_window_bits", "10")], + (False, False, 15, 10), + ), + ( + (False, False, None, True), + [("client_max_window_bits", "16")], + NegotiationError, + ), + ((False, False, None, 12), [], (False, False, 15, 12)), + ( + (False, False, None, 12), + [("client_max_window_bits", "10")], + (False, False, 15, 10), + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "12")], + (False, False, 15, 12), + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "13")], + NegotiationError, + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "12")] * 2, + DuplicateParameter, + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "42")], + InvalidParameterValue, + ), + # Test all parameters together + ( + (True, True, 12, 12), + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "10"), + ("client_max_window_bits", "10"), + ], + (True, True, 10, 10), + ), + ( + (False, False, None, True), + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "10"), + ("client_max_window_bits", "10"), + ], + (True, True, 10, 10), + ), + ( + (True, True, 12, 12), + [ + ("server_no_context_takeover", None), + ("server_max_window_bits", "12"), + ], + (True, True, 12, 12), + ), + ]: + with self.subTest(config=config, response_params=response_params): + factory = ClientPerMessageDeflateFactory(*config) + if isinstance(result, type) and issubclass(result, Exception): + with self.assertRaises(result): + factory.process_response_params(response_params, []) + else: + extension = factory.process_response_params(response_params, []) + expected = PerMessageDeflate(*result) + self.assertExtensionEqual(extension, expected) + + def test_process_response_params_deduplication(self): + factory = ClientPerMessageDeflateFactory(False, False, None, None) + with self.assertRaises(NegotiationError): + factory.process_response_params( + [], [PerMessageDeflate(False, False, 15, 15)] + ) + + +class ServerPerMessageDeflateFactoryTests(unittest.TestCase, ExtensionTestsMixin): + def test_name(self): + assert ServerPerMessageDeflateFactory.name == "permessage-deflate" + + def test_init(self): + for config in [ + (False, False, 8, None), # server_max_window_bits ≥ 8 + (False, True, 15, None), # server_max_window_bits ≤ 15 + (True, False, None, 8), # client_max_window_bits ≥ 8 + (True, True, None, 15), # client_max_window_bits ≤ 15 + (False, False, None, None, {"memLevel": 4}), + ]: + with self.subTest(config=config): + # This does not raise an exception. + ServerPerMessageDeflateFactory(*config) + + def test_init_error(self): + for config in [ + (False, False, 7, 8), # server_max_window_bits < 8 + (False, True, 8, 7), # client_max_window_bits < 8 + (True, False, 16, 15), # server_max_window_bits > 15 + (True, True, 15, 16), # client_max_window_bits > 15 + (False, False, None, True), # client_max_window_bits + (False, False, True, None), # server_max_window_bits + (False, False, None, None, {"wbits": 11}), + ]: + with self.subTest(config=config): + with self.assertRaises(ValueError): + ServerPerMessageDeflateFactory(*config) + + def test_process_request_params(self): + # Parameters in result appear swapped vs. config because the order is + # (remote, local) vs. (server, client). + for config, request_params, response_params, result in [ + # Test without any parameter + ((False, False, None, None), [], [], (False, False, 15, 15)), + ( + (False, False, None, None), + [("unknown", None)], + None, + InvalidParameterName, + ), + # Test server_no_context_takeover + ( + (False, False, None, None), + [("server_no_context_takeover", None)], + [("server_no_context_takeover", None)], + (False, True, 15, 15), + ), + ( + (True, False, None, None), + [], + [("server_no_context_takeover", None)], + (False, True, 15, 15), + ), + ( + (True, False, None, None), + [("server_no_context_takeover", None)], + [("server_no_context_takeover", None)], + (False, True, 15, 15), + ), + ( + (True, False, None, None), + [("server_no_context_takeover", None)] * 2, + None, + DuplicateParameter, + ), + ( + (True, False, None, None), + [("server_no_context_takeover", "42")], + None, + InvalidParameterValue, + ), + # Test client_no_context_takeover + ( + (False, False, None, None), + [("client_no_context_takeover", None)], + [("client_no_context_takeover", None)], # doesn't matter + (True, False, 15, 15), + ), + ( + (False, True, None, None), + [], + [("client_no_context_takeover", None)], + (True, False, 15, 15), + ), + ( + (False, True, None, None), + [("client_no_context_takeover", None)], + [("client_no_context_takeover", None)], # doesn't matter + (True, False, 15, 15), + ), + ( + (False, True, None, None), + [("client_no_context_takeover", None)] * 2, + None, + DuplicateParameter, + ), + ( + (False, True, None, None), + [("client_no_context_takeover", "42")], + None, + InvalidParameterValue, + ), + # Test server_max_window_bits + ( + (False, False, None, None), + [("server_max_window_bits", "7")], + None, + NegotiationError, + ), + ( + (False, False, None, None), + [("server_max_window_bits", "10")], + [("server_max_window_bits", "10")], + (False, False, 15, 10), + ), + ( + (False, False, None, None), + [("server_max_window_bits", "16")], + None, + NegotiationError, + ), + ( + (False, False, 12, None), + [], + [("server_max_window_bits", "12")], + (False, False, 15, 12), + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "10")], + [("server_max_window_bits", "10")], + (False, False, 15, 10), + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "12")], + [("server_max_window_bits", "12")], + (False, False, 15, 12), + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "13")], + [("server_max_window_bits", "12")], + (False, False, 15, 12), + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "12")] * 2, + None, + DuplicateParameter, + ), + ( + (False, False, 12, None), + [("server_max_window_bits", "42")], + None, + InvalidParameterValue, + ), + # Test client_max_window_bits + ( + (False, False, None, None), + [("client_max_window_bits", None)], + [], + (False, False, 15, 15), + ), + ( + (False, False, None, None), + [("client_max_window_bits", "7")], + None, + InvalidParameterValue, + ), + ( + (False, False, None, None), + [("client_max_window_bits", "10")], + [("client_max_window_bits", "10")], # doesn't matter + (False, False, 10, 15), + ), + ( + (False, False, None, None), + [("client_max_window_bits", "16")], + None, + InvalidParameterValue, + ), + ((False, False, None, 12), [], None, NegotiationError), + ( + (False, False, None, 12), + [("client_max_window_bits", None)], + [("client_max_window_bits", "12")], + (False, False, 12, 15), + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "10")], + [("client_max_window_bits", "10")], + (False, False, 10, 15), + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "12")], + [("client_max_window_bits", "12")], # doesn't matter + (False, False, 12, 15), + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "13")], + [("client_max_window_bits", "12")], # doesn't matter + (False, False, 12, 15), + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "12")] * 2, + None, + DuplicateParameter, + ), + ( + (False, False, None, 12), + [("client_max_window_bits", "42")], + None, + InvalidParameterValue, + ), + # # Test all parameters together + ( + (True, True, 12, 12), + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "10"), + ("client_max_window_bits", "10"), + ], + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "10"), + ("client_max_window_bits", "10"), + ], + (True, True, 10, 10), + ), + ( + (False, False, None, None), + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "10"), + ("client_max_window_bits", "10"), + ], + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "10"), + ("client_max_window_bits", "10"), + ], + (True, True, 10, 10), + ), + ( + (True, True, 12, 12), + [("client_max_window_bits", None)], + [ + ("server_no_context_takeover", None), + ("client_no_context_takeover", None), + ("server_max_window_bits", "12"), + ("client_max_window_bits", "12"), + ], + (True, True, 12, 12), + ), + ]: + with self.subTest( + config=config, + request_params=request_params, + response_params=response_params, + ): + factory = ServerPerMessageDeflateFactory(*config) + if isinstance(result, type) and issubclass(result, Exception): + with self.assertRaises(result): + factory.process_request_params(request_params, []) + else: + params, extension = factory.process_request_params( + request_params, [] + ) + self.assertEqual(params, response_params) + expected = PerMessageDeflate(*result) + self.assertExtensionEqual(extension, expected) + + def test_process_response_params_deduplication(self): + factory = ServerPerMessageDeflateFactory(False, False, None, None) + with self.assertRaises(NegotiationError): + factory.process_request_params( + [], [PerMessageDeflate(False, False, 15, 15)] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_auth.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_auth.py new file mode 100644 index 00000000000..97a4485a0ff --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_auth.py @@ -0,0 +1,139 @@ +import unittest +import urllib.error + +from websockets.auth import * +from websockets.auth import is_credentials +from websockets.exceptions import InvalidStatusCode +from websockets.headers import build_authorization_basic + +from .test_client_server import ClientServerTestsMixin, with_client, with_server +from .utils import AsyncioTestCase + + +class AuthTests(unittest.TestCase): + def test_is_credentials(self): + self.assertTrue(is_credentials(("username", "password"))) + + def test_is_not_credentials(self): + self.assertFalse(is_credentials(None)) + self.assertFalse(is_credentials("username")) + + +class AuthClientServerTests(ClientServerTestsMixin, AsyncioTestCase): + + create_protocol = basic_auth_protocol_factory( + realm="auth-tests", credentials=("hello", "iloveyou") + ) + + @with_server(create_protocol=create_protocol) + @with_client(user_info=("hello", "iloveyou")) + def test_basic_auth(self): + req_headers = self.client.request_headers + resp_headers = self.client.response_headers + self.assertEqual(req_headers["Authorization"], "Basic aGVsbG86aWxvdmV5b3U=") + self.assertNotIn("WWW-Authenticate", resp_headers) + + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + + def test_basic_auth_server_no_credentials(self): + with self.assertRaises(TypeError) as raised: + basic_auth_protocol_factory(realm="auth-tests", credentials=None) + self.assertEqual( + str(raised.exception), "provide either credentials or check_credentials" + ) + + def test_basic_auth_server_bad_credentials(self): + with self.assertRaises(TypeError) as raised: + basic_auth_protocol_factory(realm="auth-tests", credentials=42) + self.assertEqual(str(raised.exception), "invalid credentials argument: 42") + + create_protocol_multiple_credentials = basic_auth_protocol_factory( + realm="auth-tests", + credentials=[("hello", "iloveyou"), ("goodbye", "stillloveu")], + ) + + @with_server(create_protocol=create_protocol_multiple_credentials) + @with_client(user_info=("hello", "iloveyou")) + def test_basic_auth_server_multiple_credentials(self): + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + + def test_basic_auth_bad_multiple_credentials(self): + with self.assertRaises(TypeError) as raised: + basic_auth_protocol_factory( + realm="auth-tests", credentials=[("hello", "iloveyou"), 42] + ) + self.assertEqual( + str(raised.exception), + "invalid credentials argument: [('hello', 'iloveyou'), 42]", + ) + + async def check_credentials(username, password): + return password == "iloveyou" + + create_protocol_check_credentials = basic_auth_protocol_factory( + realm="auth-tests", check_credentials=check_credentials + ) + + @with_server(create_protocol=create_protocol_check_credentials) + @with_client(user_info=("hello", "iloveyou")) + def test_basic_auth_check_credentials(self): + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + + @with_server(create_protocol=create_protocol) + def test_basic_auth_missing_credentials(self): + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client() + self.assertEqual(raised.exception.status_code, 401) + + @with_server(create_protocol=create_protocol) + def test_basic_auth_missing_credentials_details(self): + with self.assertRaises(urllib.error.HTTPError) as raised: + self.loop.run_until_complete(self.make_http_request()) + self.assertEqual(raised.exception.code, 401) + self.assertEqual( + raised.exception.headers["WWW-Authenticate"], + 'Basic realm="auth-tests", charset="UTF-8"', + ) + self.assertEqual(raised.exception.read().decode(), "Missing credentials\n") + + @with_server(create_protocol=create_protocol) + def test_basic_auth_unsupported_credentials(self): + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client(extra_headers={"Authorization": "Digest ..."}) + self.assertEqual(raised.exception.status_code, 401) + + @with_server(create_protocol=create_protocol) + def test_basic_auth_unsupported_credentials_details(self): + with self.assertRaises(urllib.error.HTTPError) as raised: + self.loop.run_until_complete( + self.make_http_request(headers={"Authorization": "Digest ..."}) + ) + self.assertEqual(raised.exception.code, 401) + self.assertEqual( + raised.exception.headers["WWW-Authenticate"], + 'Basic realm="auth-tests", charset="UTF-8"', + ) + self.assertEqual(raised.exception.read().decode(), "Unsupported credentials\n") + + @with_server(create_protocol=create_protocol) + def test_basic_auth_invalid_credentials(self): + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client(user_info=("hello", "ihateyou")) + self.assertEqual(raised.exception.status_code, 401) + + @with_server(create_protocol=create_protocol) + def test_basic_auth_invalid_credentials_details(self): + with self.assertRaises(urllib.error.HTTPError) as raised: + authorization = build_authorization_basic("hello", "ihateyou") + self.loop.run_until_complete( + self.make_http_request(headers={"Authorization": authorization}) + ) + self.assertEqual(raised.exception.code, 401) + self.assertEqual( + raised.exception.headers["WWW-Authenticate"], + 'Basic realm="auth-tests", charset="UTF-8"', + ) + self.assertEqual(raised.exception.read().decode(), "Invalid credentials\n") diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_client_server.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_client_server.py new file mode 100644 index 00000000000..35913666c57 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_client_server.py @@ -0,0 +1,1546 @@ +import asyncio +import contextlib +import functools +import http +import pathlib +import random +import socket +import ssl +import tempfile +import unittest +import unittest.mock +import urllib.error +import urllib.request +import warnings + +from websockets.client import * +from websockets.exceptions import ( + ConnectionClosed, + InvalidHandshake, + InvalidHeader, + InvalidStatusCode, + NegotiationError, +) +from websockets.extensions.permessage_deflate import ( + ClientPerMessageDeflateFactory, + PerMessageDeflate, + ServerPerMessageDeflateFactory, +) +from websockets.handshake import build_response +from websockets.http import USER_AGENT, Headers, read_response +from websockets.protocol import State +from websockets.server import * +from websockets.uri import parse_uri + +from .test_protocol import MS +from .utils import AsyncioTestCase + + +# Generate TLS certificate with: +# $ openssl req -x509 -config test_localhost.cnf -days 15340 -newkey rsa:2048 \ +# -out test_localhost.crt -keyout test_localhost.key +# $ cat test_localhost.key test_localhost.crt > test_localhost.pem +# $ rm test_localhost.key test_localhost.crt + +testcert = bytes(pathlib.Path(__file__).with_name("test_localhost.pem")) + + +async def handler(ws, path): + if path == "/deprecated_attributes": + await ws.recv() # delay that allows catching warnings + await ws.send(repr((ws.host, ws.port, ws.secure))) + elif path == "/close_timeout": + await ws.send(repr(ws.close_timeout)) + elif path == "/path": + await ws.send(str(ws.path)) + elif path == "/headers": + await ws.send(repr(ws.request_headers)) + await ws.send(repr(ws.response_headers)) + elif path == "/extensions": + await ws.send(repr(ws.extensions)) + elif path == "/subprotocol": + await ws.send(repr(ws.subprotocol)) + elif path == "/slow_stop": + await ws.wait_closed() + await asyncio.sleep(2 * MS) + else: + await ws.send((await ws.recv())) + + +@contextlib.contextmanager +def temp_test_server(test, **kwargs): + test.start_server(**kwargs) + try: + yield + finally: + test.stop_server() + + +@contextlib.contextmanager +def temp_test_redirecting_server( + test, status, include_location=True, force_insecure=False +): + test.start_redirecting_server(status, include_location, force_insecure) + try: + yield + finally: + test.stop_redirecting_server() + + +@contextlib.contextmanager +def temp_test_client(test, *args, **kwargs): + test.start_client(*args, **kwargs) + try: + yield + finally: + test.stop_client() + + +def with_manager(manager, *args, **kwargs): + """ + Return a decorator that wraps a function with a context manager. + + """ + + def decorate(func): + @functools.wraps(func) + def _decorate(self, *_args, **_kwargs): + with manager(self, *args, **kwargs): + return func(self, *_args, **_kwargs) + + return _decorate + + return decorate + + +def with_server(**kwargs): + """ + Return a decorator for TestCase methods that starts and stops a server. + + """ + return with_manager(temp_test_server, **kwargs) + + +def with_client(*args, **kwargs): + """ + Return a decorator for TestCase methods that starts and stops a client. + + """ + return with_manager(temp_test_client, *args, **kwargs) + + +def get_server_uri(server, secure=False, resource_name="/", user_info=None): + """ + Return a WebSocket URI for connecting to the given server. + + """ + proto = "wss" if secure else "ws" + + user_info = ":".join(user_info) + "@" if user_info else "" + + # Pick a random socket in order to test both IPv4 and IPv6 on systems + # where both are available. Randomizing tests is usually a bad idea. If + # needed, either use the first socket, or test separately IPv4 and IPv6. + server_socket = random.choice(server.sockets) + + if server_socket.family == socket.AF_INET6: # pragma: no cover + host, port = server_socket.getsockname()[:2] # (no IPv6 on CI) + host = f"[{host}]" + elif server_socket.family == socket.AF_INET: + host, port = server_socket.getsockname() + else: # pragma: no cover + raise ValueError("expected an IPv6, IPv4, or Unix socket") + + return f"{proto}://{user_info}{host}:{port}{resource_name}" + + +class UnauthorizedServerProtocol(WebSocketServerProtocol): + async def process_request(self, path, request_headers): + # Test returning headers as a Headers instance (1/3) + return http.HTTPStatus.UNAUTHORIZED, Headers([("X-Access", "denied")]), b"" + + +class ForbiddenServerProtocol(WebSocketServerProtocol): + async def process_request(self, path, request_headers): + # Test returning headers as a dict (2/3) + return http.HTTPStatus.FORBIDDEN, {"X-Access": "denied"}, b"" + + +class HealthCheckServerProtocol(WebSocketServerProtocol): + async def process_request(self, path, request_headers): + # Test returning headers as a list of pairs (3/3) + if path == "/__health__/": + return http.HTTPStatus.OK, [("X-Access", "OK")], b"status = green\n" + + +class SlowOpeningHandshakeProtocol(WebSocketServerProtocol): + async def process_request(self, path, request_headers): + await asyncio.sleep(10 * MS) + + +class FooClientProtocol(WebSocketClientProtocol): + pass + + +class BarClientProtocol(WebSocketClientProtocol): + pass + + +class ClientNoOpExtensionFactory: + name = "x-no-op" + + def get_request_params(self): + return [] + + def process_response_params(self, params, accepted_extensions): + if params: + raise NegotiationError() + return NoOpExtension() + + +class ServerNoOpExtensionFactory: + name = "x-no-op" + + def __init__(self, params=None): + self.params = params or [] + + def process_request_params(self, params, accepted_extensions): + return self.params, NoOpExtension() + + +class NoOpExtension: + name = "x-no-op" + + def __repr__(self): + return "NoOpExtension()" + + def decode(self, frame, *, max_size=None): + return frame + + def encode(self, frame): + return frame + + +class ClientServerTestsMixin: + + secure = False + + def setUp(self): + super().setUp() + self.server = None + self.redirecting_server = None + + @property + def server_context(self): + return None + + def start_server(self, deprecation_warnings=None, **kwargs): + # Disable compression by default in tests. + kwargs.setdefault("compression", None) + # Disable pings by default in tests. + kwargs.setdefault("ping_interval", None) + + with warnings.catch_warnings(record=True) as recorded_warnings: + start_server = serve(handler, "localhost", 0, **kwargs) + self.server = self.loop.run_until_complete(start_server) + + expected_warnings = [] if deprecation_warnings is None else deprecation_warnings + self.assertDeprecationWarnings(recorded_warnings, expected_warnings) + + def start_redirecting_server( + self, status, include_location=True, force_insecure=False + ): + async def process_request(path, headers): + server_uri = get_server_uri(self.server, self.secure, path) + if force_insecure: + server_uri = server_uri.replace("wss:", "ws:") + headers = {"Location": server_uri} if include_location else [] + return status, headers, b"" + + start_server = serve( + handler, + "localhost", + 0, + compression=None, + ping_interval=None, + process_request=process_request, + ssl=self.server_context, + ) + self.redirecting_server = self.loop.run_until_complete(start_server) + + def start_client( + self, resource_name="/", user_info=None, deprecation_warnings=None, **kwargs + ): + # Disable compression by default in tests. + kwargs.setdefault("compression", None) + # Disable pings by default in tests. + kwargs.setdefault("ping_interval", None) + secure = kwargs.get("ssl") is not None + try: + server_uri = kwargs.pop("uri") + except KeyError: + server = self.redirecting_server if self.redirecting_server else self.server + server_uri = get_server_uri(server, secure, resource_name, user_info) + + with warnings.catch_warnings(record=True) as recorded_warnings: + start_client = connect(server_uri, **kwargs) + self.client = self.loop.run_until_complete(start_client) + + expected_warnings = [] if deprecation_warnings is None else deprecation_warnings + self.assertDeprecationWarnings(recorded_warnings, expected_warnings) + + def stop_client(self): + try: + self.loop.run_until_complete( + asyncio.wait_for(self.client.close_connection_task, timeout=1) + ) + except asyncio.TimeoutError: # pragma: no cover + self.fail("Client failed to stop") + + def stop_server(self): + self.server.close() + try: + self.loop.run_until_complete( + asyncio.wait_for(self.server.wait_closed(), timeout=1) + ) + except asyncio.TimeoutError: # pragma: no cover + self.fail("Server failed to stop") + + def stop_redirecting_server(self): + self.redirecting_server.close() + try: + self.loop.run_until_complete( + asyncio.wait_for(self.redirecting_server.wait_closed(), timeout=1) + ) + except asyncio.TimeoutError: # pragma: no cover + self.fail("Redirecting server failed to stop") + finally: + self.redirecting_server = None + + @contextlib.contextmanager + def temp_server(self, **kwargs): + with temp_test_server(self, **kwargs): + yield + + @contextlib.contextmanager + def temp_client(self, *args, **kwargs): + with temp_test_client(self, *args, **kwargs): + yield + + def make_http_request(self, path="/", headers=None): + if headers is None: + headers = {} + + # Set url to 'https?://<host>:<port><path>'. + url = get_server_uri( + self.server, resource_name=path, secure=self.secure + ).replace("ws", "http") + + request = urllib.request.Request(url=url, headers=headers) + + if self.secure: + open_health_check = functools.partial( + urllib.request.urlopen, request, context=self.client_context + ) + else: + open_health_check = functools.partial(urllib.request.urlopen, request) + + return self.loop.run_in_executor(None, open_health_check) + + +class SecureClientServerTestsMixin(ClientServerTestsMixin): + + secure = True + + @property + def server_context(self): + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ssl_context.load_cert_chain(testcert) + return ssl_context + + @property + def client_context(self): + ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ssl_context.load_verify_locations(testcert) + return ssl_context + + def start_server(self, **kwargs): + kwargs.setdefault("ssl", self.server_context) + super().start_server(**kwargs) + + def start_client(self, path="/", **kwargs): + kwargs.setdefault("ssl", self.client_context) + super().start_client(path, **kwargs) + + +class CommonClientServerTests: + """ + Mixin that defines most tests but doesn't inherit unittest.TestCase. + + Tests are run by the ClientServerTests and SecureClientServerTests subclasses. + + """ + + @with_server() + @with_client() + def test_basic(self): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + @with_server() + def test_redirect(self): + redirect_statuses = [ + http.HTTPStatus.MOVED_PERMANENTLY, + http.HTTPStatus.FOUND, + http.HTTPStatus.SEE_OTHER, + http.HTTPStatus.TEMPORARY_REDIRECT, + http.HTTPStatus.PERMANENT_REDIRECT, + ] + for status in redirect_statuses: + with temp_test_redirecting_server(self, status): + with temp_test_client(self): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + def test_infinite_redirect(self): + with temp_test_redirecting_server(self, http.HTTPStatus.FOUND): + self.server = self.redirecting_server + with self.assertRaises(InvalidHandshake): + with temp_test_client(self): + self.fail("Did not raise") # pragma: no cover + + @with_server() + def test_redirect_missing_location(self): + with temp_test_redirecting_server( + self, http.HTTPStatus.FOUND, include_location=False + ): + with self.assertRaises(InvalidHeader): + with temp_test_client(self): + self.fail("Did not raise") # pragma: no cover + + def test_explicit_event_loop(self): + with self.temp_server(loop=self.loop): + with self.temp_client(loop=self.loop): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + @with_server() + def test_explicit_host_port(self): + uri = get_server_uri(self.server, self.secure) + wsuri = parse_uri(uri) + + # Change host and port to invalid values. + changed_uri = uri.replace(wsuri.host, "example.com").replace( + str(wsuri.port), str(65535 - wsuri.port) + ) + + with self.temp_client(uri=changed_uri, host=wsuri.host, port=wsuri.port): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + @with_server() + def test_explicit_socket(self): + class TrackedSocket(socket.socket): + def __init__(self, *args, **kwargs): + self.used_for_read = False + self.used_for_write = False + super().__init__(*args, **kwargs) + + def recv(self, *args, **kwargs): + self.used_for_read = True + return super().recv(*args, **kwargs) + + def send(self, *args, **kwargs): + self.used_for_write = True + return super().send(*args, **kwargs) + + server_socket = [ + sock for sock in self.server.sockets if sock.family == socket.AF_INET + ][0] + client_socket = TrackedSocket(socket.AF_INET, socket.SOCK_STREAM) + client_socket.connect(server_socket.getsockname()) + + try: + self.assertFalse(client_socket.used_for_read) + self.assertFalse(client_socket.used_for_write) + + with self.temp_client( + sock=client_socket, + # "You must set server_hostname when using ssl without a host" + server_hostname="localhost" if self.secure else None, + ): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + self.assertTrue(client_socket.used_for_read) + self.assertTrue(client_socket.used_for_write) + + finally: + client_socket.close() + + @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets") + def test_unix_socket(self): + with tempfile.TemporaryDirectory() as temp_dir: + path = bytes(pathlib.Path(temp_dir) / "websockets") + + # Like self.start_server() but with unix_serve(). + unix_server = unix_serve(handler, path) + self.server = self.loop.run_until_complete(unix_server) + try: + # Like self.start_client() but with unix_connect() + unix_client = unix_connect(path) + self.client = self.loop.run_until_complete(unix_client) + try: + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + finally: + self.stop_client() + finally: + self.stop_server() + + async def process_request_OK(path, request_headers): + return http.HTTPStatus.OK, [], b"OK\n" + + @with_server(process_request=process_request_OK) + def test_process_request_argument(self): + response = self.loop.run_until_complete(self.make_http_request("/")) + + with contextlib.closing(response): + self.assertEqual(response.code, 200) + + def legacy_process_request_OK(path, request_headers): + return http.HTTPStatus.OK, [], b"OK\n" + + @with_server(process_request=legacy_process_request_OK) + def test_process_request_argument_backwards_compatibility(self): + with warnings.catch_warnings(record=True) as recorded_warnings: + response = self.loop.run_until_complete(self.make_http_request("/")) + + with contextlib.closing(response): + self.assertEqual(response.code, 200) + + self.assertDeprecationWarnings( + recorded_warnings, ["declare process_request as a coroutine"] + ) + + class ProcessRequestOKServerProtocol(WebSocketServerProtocol): + async def process_request(self, path, request_headers): + return http.HTTPStatus.OK, [], b"OK\n" + + @with_server(create_protocol=ProcessRequestOKServerProtocol) + def test_process_request_override(self): + response = self.loop.run_until_complete(self.make_http_request("/")) + + with contextlib.closing(response): + self.assertEqual(response.code, 200) + + class LegacyProcessRequestOKServerProtocol(WebSocketServerProtocol): + def process_request(self, path, request_headers): + return http.HTTPStatus.OK, [], b"OK\n" + + @with_server(create_protocol=LegacyProcessRequestOKServerProtocol) + def test_process_request_override_backwards_compatibility(self): + with warnings.catch_warnings(record=True) as recorded_warnings: + response = self.loop.run_until_complete(self.make_http_request("/")) + + with contextlib.closing(response): + self.assertEqual(response.code, 200) + + self.assertDeprecationWarnings( + recorded_warnings, ["declare process_request as a coroutine"] + ) + + def select_subprotocol_chat(client_subprotocols, server_subprotocols): + return "chat" + + @with_server( + subprotocols=["superchat", "chat"], select_subprotocol=select_subprotocol_chat + ) + @with_client("/subprotocol", subprotocols=["superchat", "chat"]) + def test_select_subprotocol_argument(self): + server_subprotocol = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_subprotocol, repr("chat")) + self.assertEqual(self.client.subprotocol, "chat") + + class SelectSubprotocolChatServerProtocol(WebSocketServerProtocol): + def select_subprotocol(self, client_subprotocols, server_subprotocols): + return "chat" + + @with_server( + subprotocols=["superchat", "chat"], + create_protocol=SelectSubprotocolChatServerProtocol, + ) + @with_client("/subprotocol", subprotocols=["superchat", "chat"]) + def test_select_subprotocol_override(self): + server_subprotocol = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_subprotocol, repr("chat")) + self.assertEqual(self.client.subprotocol, "chat") + + @with_server() + @with_client("/deprecated_attributes") + def test_protocol_deprecated_attributes(self): + # The test could be connecting with IPv6 or IPv4. + expected_client_attrs = [ + server_socket.getsockname()[:2] + (self.secure,) + for server_socket in self.server.sockets + ] + with warnings.catch_warnings(record=True) as recorded_warnings: + client_attrs = (self.client.host, self.client.port, self.client.secure) + self.assertDeprecationWarnings( + recorded_warnings, + [ + "use remote_address[0] instead of host", + "use remote_address[1] instead of port", + "don't use secure", + ], + ) + self.assertIn(client_attrs, expected_client_attrs) + + expected_server_attrs = ("localhost", 0, self.secure) + with warnings.catch_warnings(record=True) as recorded_warnings: + self.loop.run_until_complete(self.client.send("")) + server_attrs = self.loop.run_until_complete(self.client.recv()) + self.assertDeprecationWarnings( + recorded_warnings, + [ + "use local_address[0] instead of host", + "use local_address[1] instead of port", + "don't use secure", + ], + ) + self.assertEqual(server_attrs, repr(expected_server_attrs)) + + @with_server() + @with_client("/path") + def test_protocol_path(self): + client_path = self.client.path + self.assertEqual(client_path, "/path") + server_path = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_path, "/path") + + @with_server() + @with_client("/headers") + def test_protocol_headers(self): + client_req = self.client.request_headers + client_resp = self.client.response_headers + self.assertEqual(client_req["User-Agent"], USER_AGENT) + self.assertEqual(client_resp["Server"], USER_AGENT) + server_req = self.loop.run_until_complete(self.client.recv()) + server_resp = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_req, repr(client_req)) + self.assertEqual(server_resp, repr(client_resp)) + + @with_server() + @with_client("/headers", extra_headers=Headers({"X-Spam": "Eggs"})) + def test_protocol_custom_request_headers(self): + req_headers = self.loop.run_until_complete(self.client.recv()) + self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", req_headers) + + @with_server() + @with_client("/headers", extra_headers={"X-Spam": "Eggs"}) + def test_protocol_custom_request_headers_dict(self): + req_headers = self.loop.run_until_complete(self.client.recv()) + self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", req_headers) + + @with_server() + @with_client("/headers", extra_headers=[("X-Spam", "Eggs")]) + def test_protocol_custom_request_headers_list(self): + req_headers = self.loop.run_until_complete(self.client.recv()) + self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", req_headers) + + @with_server() + @with_client("/headers", extra_headers=[("User-Agent", "Eggs")]) + def test_protocol_custom_request_user_agent(self): + req_headers = self.loop.run_until_complete(self.client.recv()) + self.loop.run_until_complete(self.client.recv()) + self.assertEqual(req_headers.count("User-Agent"), 1) + self.assertIn("('User-Agent', 'Eggs')", req_headers) + + @with_server(extra_headers=lambda p, r: Headers({"X-Spam": "Eggs"})) + @with_client("/headers") + def test_protocol_custom_response_headers_callable(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", resp_headers) + + @with_server(extra_headers=lambda p, r: {"X-Spam": "Eggs"}) + @with_client("/headers") + def test_protocol_custom_response_headers_callable_dict(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", resp_headers) + + @with_server(extra_headers=lambda p, r: [("X-Spam", "Eggs")]) + @with_client("/headers") + def test_protocol_custom_response_headers_callable_list(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", resp_headers) + + @with_server(extra_headers=lambda p, r: None) + @with_client("/headers") + def test_protocol_custom_response_headers_callable_none(self): + self.loop.run_until_complete(self.client.recv()) # doesn't crash + self.loop.run_until_complete(self.client.recv()) # nothing to check + + @with_server(extra_headers=Headers({"X-Spam": "Eggs"})) + @with_client("/headers") + def test_protocol_custom_response_headers(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", resp_headers) + + @with_server(extra_headers={"X-Spam": "Eggs"}) + @with_client("/headers") + def test_protocol_custom_response_headers_dict(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", resp_headers) + + @with_server(extra_headers=[("X-Spam", "Eggs")]) + @with_client("/headers") + def test_protocol_custom_response_headers_list(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertIn("('X-Spam', 'Eggs')", resp_headers) + + @with_server(extra_headers=[("Server", "Eggs")]) + @with_client("/headers") + def test_protocol_custom_response_user_agent(self): + self.loop.run_until_complete(self.client.recv()) + resp_headers = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(resp_headers.count("Server"), 1) + self.assertIn("('Server', 'Eggs')", resp_headers) + + @with_server(create_protocol=HealthCheckServerProtocol) + def test_http_request_http_endpoint(self): + # Making a HTTP request to a HTTP endpoint succeeds. + response = self.loop.run_until_complete(self.make_http_request("/__health__/")) + + with contextlib.closing(response): + self.assertEqual(response.code, 200) + self.assertEqual(response.read(), b"status = green\n") + + @with_server(create_protocol=HealthCheckServerProtocol) + def test_http_request_ws_endpoint(self): + # Making a HTTP request to a WS endpoint fails. + with self.assertRaises(urllib.error.HTTPError) as raised: + self.loop.run_until_complete(self.make_http_request()) + + self.assertEqual(raised.exception.code, 426) + self.assertEqual(raised.exception.headers["Upgrade"], "websocket") + + @with_server(create_protocol=HealthCheckServerProtocol) + def test_ws_connection_http_endpoint(self): + # Making a WS connection to a HTTP endpoint fails. + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client("/__health__/") + + self.assertEqual(raised.exception.status_code, 200) + + @with_server(create_protocol=HealthCheckServerProtocol) + def test_ws_connection_ws_endpoint(self): + # Making a WS connection to a WS endpoint succeeds. + self.start_client() + self.loop.run_until_complete(self.client.send("Hello!")) + self.loop.run_until_complete(self.client.recv()) + self.stop_client() + + def assert_client_raises_code(self, status_code): + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client() + self.assertEqual(raised.exception.status_code, status_code) + + @with_server(create_protocol=UnauthorizedServerProtocol) + def test_server_create_protocol(self): + self.assert_client_raises_code(401) + + def create_unauthorized_server_protocol(*args, **kwargs): + return UnauthorizedServerProtocol(*args, **kwargs) + + @with_server(create_protocol=create_unauthorized_server_protocol) + def test_server_create_protocol_function(self): + self.assert_client_raises_code(401) + + @with_server( + klass=UnauthorizedServerProtocol, + deprecation_warnings=["rename klass to create_protocol"], + ) + def test_server_klass_backwards_compatibility(self): + self.assert_client_raises_code(401) + + @with_server( + create_protocol=ForbiddenServerProtocol, + klass=UnauthorizedServerProtocol, + deprecation_warnings=["rename klass to create_protocol"], + ) + def test_server_create_protocol_over_klass(self): + self.assert_client_raises_code(403) + + @with_server() + @with_client("/path", create_protocol=FooClientProtocol) + def test_client_create_protocol(self): + self.assertIsInstance(self.client, FooClientProtocol) + + @with_server() + @with_client( + "/path", + create_protocol=(lambda *args, **kwargs: FooClientProtocol(*args, **kwargs)), + ) + def test_client_create_protocol_function(self): + self.assertIsInstance(self.client, FooClientProtocol) + + @with_server() + @with_client( + "/path", + klass=FooClientProtocol, + deprecation_warnings=["rename klass to create_protocol"], + ) + def test_client_klass(self): + self.assertIsInstance(self.client, FooClientProtocol) + + @with_server() + @with_client( + "/path", + create_protocol=BarClientProtocol, + klass=FooClientProtocol, + deprecation_warnings=["rename klass to create_protocol"], + ) + def test_client_create_protocol_over_klass(self): + self.assertIsInstance(self.client, BarClientProtocol) + + @with_server(close_timeout=7) + @with_client("/close_timeout") + def test_server_close_timeout(self): + close_timeout = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(eval(close_timeout), 7) + + @with_server(timeout=6, deprecation_warnings=["rename timeout to close_timeout"]) + @with_client("/close_timeout") + def test_server_timeout_backwards_compatibility(self): + close_timeout = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(eval(close_timeout), 6) + + @with_server( + close_timeout=7, + timeout=6, + deprecation_warnings=["rename timeout to close_timeout"], + ) + @with_client("/close_timeout") + def test_server_close_timeout_over_timeout(self): + close_timeout = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(eval(close_timeout), 7) + + @with_server() + @with_client("/close_timeout", close_timeout=7) + def test_client_close_timeout(self): + self.assertEqual(self.client.close_timeout, 7) + + @with_server() + @with_client( + "/close_timeout", + timeout=6, + deprecation_warnings=["rename timeout to close_timeout"], + ) + def test_client_timeout_backwards_compatibility(self): + self.assertEqual(self.client.close_timeout, 6) + + @with_server() + @with_client( + "/close_timeout", + close_timeout=7, + timeout=6, + deprecation_warnings=["rename timeout to close_timeout"], + ) + def test_client_close_timeout_over_timeout(self): + self.assertEqual(self.client.close_timeout, 7) + + @with_server() + @with_client("/extensions") + def test_no_extension(self): + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_extensions, repr([])) + self.assertEqual(repr(self.client.extensions), repr([])) + + @with_server(extensions=[ServerNoOpExtensionFactory()]) + @with_client("/extensions", extensions=[ClientNoOpExtensionFactory()]) + def test_extension(self): + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_extensions, repr([NoOpExtension()])) + self.assertEqual(repr(self.client.extensions), repr([NoOpExtension()])) + + @with_server() + @with_client("/extensions", extensions=[ClientNoOpExtensionFactory()]) + def test_extension_not_accepted(self): + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_extensions, repr([])) + self.assertEqual(repr(self.client.extensions), repr([])) + + @with_server(extensions=[ServerNoOpExtensionFactory()]) + @with_client("/extensions") + def test_extension_not_requested(self): + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_extensions, repr([])) + self.assertEqual(repr(self.client.extensions), repr([])) + + @with_server(extensions=[ServerNoOpExtensionFactory([("foo", None)])]) + def test_extension_client_rejection(self): + with self.assertRaises(NegotiationError): + self.start_client("/extensions", extensions=[ClientNoOpExtensionFactory()]) + + @with_server( + extensions=[ + # No match because the client doesn't send client_max_window_bits. + ServerPerMessageDeflateFactory(client_max_window_bits=10), + ServerPerMessageDeflateFactory(), + ] + ) + @with_client("/extensions", extensions=[ClientPerMessageDeflateFactory()]) + def test_extension_no_match_then_match(self): + # The order requested by the client has priority. + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual( + server_extensions, repr([PerMessageDeflate(False, False, 15, 15)]) + ) + self.assertEqual( + repr(self.client.extensions), + repr([PerMessageDeflate(False, False, 15, 15)]), + ) + + @with_server(extensions=[ServerPerMessageDeflateFactory()]) + @with_client("/extensions", extensions=[ClientNoOpExtensionFactory()]) + def test_extension_mismatch(self): + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_extensions, repr([])) + self.assertEqual(repr(self.client.extensions), repr([])) + + @with_server( + extensions=[ServerNoOpExtensionFactory(), ServerPerMessageDeflateFactory()] + ) + @with_client( + "/extensions", + extensions=[ClientPerMessageDeflateFactory(), ClientNoOpExtensionFactory()], + ) + def test_extension_order(self): + # The order requested by the client has priority. + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual( + server_extensions, + repr([PerMessageDeflate(False, False, 15, 15), NoOpExtension()]), + ) + self.assertEqual( + repr(self.client.extensions), + repr([PerMessageDeflate(False, False, 15, 15), NoOpExtension()]), + ) + + @with_server(extensions=[ServerNoOpExtensionFactory()]) + @unittest.mock.patch.object(WebSocketServerProtocol, "process_extensions") + def test_extensions_error(self, _process_extensions): + _process_extensions.return_value = "x-no-op", [NoOpExtension()] + + with self.assertRaises(NegotiationError): + self.start_client( + "/extensions", extensions=[ClientPerMessageDeflateFactory()] + ) + + @with_server(extensions=[ServerNoOpExtensionFactory()]) + @unittest.mock.patch.object(WebSocketServerProtocol, "process_extensions") + def test_extensions_error_no_extensions(self, _process_extensions): + _process_extensions.return_value = "x-no-op", [NoOpExtension()] + + with self.assertRaises(InvalidHandshake): + self.start_client("/extensions") + + @with_server(compression="deflate") + @with_client("/extensions", compression="deflate") + def test_compression_deflate(self): + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual( + server_extensions, repr([PerMessageDeflate(False, False, 15, 15)]) + ) + self.assertEqual( + repr(self.client.extensions), + repr([PerMessageDeflate(False, False, 15, 15)]), + ) + + @with_server( + extensions=[ + ServerPerMessageDeflateFactory( + client_no_context_takeover=True, server_max_window_bits=10 + ) + ], + compression="deflate", # overridden by explicit config + ) + @with_client( + "/extensions", + extensions=[ + ClientPerMessageDeflateFactory( + server_no_context_takeover=True, client_max_window_bits=12 + ) + ], + compression="deflate", # overridden by explicit config + ) + def test_compression_deflate_and_explicit_config(self): + server_extensions = self.loop.run_until_complete(self.client.recv()) + self.assertEqual( + server_extensions, repr([PerMessageDeflate(True, True, 12, 10)]) + ) + self.assertEqual( + repr(self.client.extensions), repr([PerMessageDeflate(True, True, 10, 12)]) + ) + + def test_compression_unsupported_server(self): + with self.assertRaises(ValueError): + self.start_server(compression="xz") + + @with_server() + def test_compression_unsupported_client(self): + with self.assertRaises(ValueError): + self.start_client(compression="xz") + + @with_server() + @with_client("/subprotocol") + def test_no_subprotocol(self): + server_subprotocol = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_subprotocol, repr(None)) + self.assertEqual(self.client.subprotocol, None) + + @with_server(subprotocols=["superchat", "chat"]) + @with_client("/subprotocol", subprotocols=["otherchat", "chat"]) + def test_subprotocol(self): + server_subprotocol = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_subprotocol, repr("chat")) + self.assertEqual(self.client.subprotocol, "chat") + + @with_server(subprotocols=["superchat"]) + @with_client("/subprotocol", subprotocols=["otherchat"]) + def test_subprotocol_not_accepted(self): + server_subprotocol = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_subprotocol, repr(None)) + self.assertEqual(self.client.subprotocol, None) + + @with_server() + @with_client("/subprotocol", subprotocols=["otherchat", "chat"]) + def test_subprotocol_not_offered(self): + server_subprotocol = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_subprotocol, repr(None)) + self.assertEqual(self.client.subprotocol, None) + + @with_server(subprotocols=["superchat", "chat"]) + @with_client("/subprotocol") + def test_subprotocol_not_requested(self): + server_subprotocol = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(server_subprotocol, repr(None)) + self.assertEqual(self.client.subprotocol, None) + + @with_server(subprotocols=["superchat"]) + @unittest.mock.patch.object(WebSocketServerProtocol, "process_subprotocol") + def test_subprotocol_error(self, _process_subprotocol): + _process_subprotocol.return_value = "superchat" + + with self.assertRaises(NegotiationError): + self.start_client("/subprotocol", subprotocols=["otherchat"]) + self.run_loop_once() + + @with_server(subprotocols=["superchat"]) + @unittest.mock.patch.object(WebSocketServerProtocol, "process_subprotocol") + def test_subprotocol_error_no_subprotocols(self, _process_subprotocol): + _process_subprotocol.return_value = "superchat" + + with self.assertRaises(InvalidHandshake): + self.start_client("/subprotocol") + self.run_loop_once() + + @with_server(subprotocols=["superchat", "chat"]) + @unittest.mock.patch.object(WebSocketServerProtocol, "process_subprotocol") + def test_subprotocol_error_two_subprotocols(self, _process_subprotocol): + _process_subprotocol.return_value = "superchat, chat" + + with self.assertRaises(InvalidHandshake): + self.start_client("/subprotocol", subprotocols=["superchat", "chat"]) + self.run_loop_once() + + @with_server() + @unittest.mock.patch("websockets.server.read_request") + def test_server_receives_malformed_request(self, _read_request): + _read_request.side_effect = ValueError("read_request failed") + + with self.assertRaises(InvalidHandshake): + self.start_client() + + @with_server() + @unittest.mock.patch("websockets.client.read_response") + def test_client_receives_malformed_response(self, _read_response): + _read_response.side_effect = ValueError("read_response failed") + + with self.assertRaises(InvalidHandshake): + self.start_client() + self.run_loop_once() + + @with_server() + @unittest.mock.patch("websockets.client.build_request") + def test_client_sends_invalid_handshake_request(self, _build_request): + def wrong_build_request(headers): + return "42" + + _build_request.side_effect = wrong_build_request + + with self.assertRaises(InvalidHandshake): + self.start_client() + + @with_server() + @unittest.mock.patch("websockets.server.build_response") + def test_server_sends_invalid_handshake_response(self, _build_response): + def wrong_build_response(headers, key): + return build_response(headers, "42") + + _build_response.side_effect = wrong_build_response + + with self.assertRaises(InvalidHandshake): + self.start_client() + + @with_server() + @unittest.mock.patch("websockets.client.read_response") + def test_server_does_not_switch_protocols(self, _read_response): + async def wrong_read_response(stream): + status_code, reason, headers = await read_response(stream) + return 400, "Bad Request", headers + + _read_response.side_effect = wrong_read_response + + with self.assertRaises(InvalidStatusCode): + self.start_client() + self.run_loop_once() + + @with_server() + @unittest.mock.patch("websockets.server.WebSocketServerProtocol.process_request") + def test_server_error_in_handshake(self, _process_request): + _process_request.side_effect = Exception("process_request crashed") + + with self.assertRaises(InvalidHandshake): + self.start_client() + + @with_server() + @unittest.mock.patch("websockets.server.WebSocketServerProtocol.send") + def test_server_handler_crashes(self, send): + send.side_effect = ValueError("send failed") + + with self.temp_client(): + self.loop.run_until_complete(self.client.send("Hello!")) + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.client.recv()) + + # Connection ends with an unexpected error. + self.assertEqual(self.client.close_code, 1011) + + @with_server() + @unittest.mock.patch("websockets.server.WebSocketServerProtocol.close") + def test_server_close_crashes(self, close): + close.side_effect = ValueError("close failed") + + with self.temp_client(): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + # Connection ends with an abnormal closure. + self.assertEqual(self.client.close_code, 1006) + + @with_server() + @with_client() + @unittest.mock.patch.object(WebSocketClientProtocol, "handshake") + def test_client_closes_connection_before_handshake(self, handshake): + # We have mocked the handshake() method to prevent the client from + # performing the opening handshake. Force it to close the connection. + self.client.transport.close() + # The server should stop properly anyway. It used to hang because the + # task handling the connection was waiting for the opening handshake. + + @with_server(create_protocol=SlowOpeningHandshakeProtocol) + def test_server_shuts_down_during_opening_handshake(self): + self.loop.call_later(5 * MS, self.server.close) + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client() + exception = raised.exception + self.assertEqual( + str(exception), "server rejected WebSocket connection: HTTP 503" + ) + self.assertEqual(exception.status_code, 503) + + @with_server() + def test_server_shuts_down_during_connection_handling(self): + with self.temp_client(): + server_ws = next(iter(self.server.websockets)) + self.server.close() + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.client.recv()) + + # Websocket connection closes properly with 1001 Going Away. + self.assertEqual(self.client.close_code, 1001) + self.assertEqual(server_ws.close_code, 1001) + + @with_server() + def test_server_shuts_down_waits_until_handlers_terminate(self): + # This handler waits a bit after the connection is closed in order + # to test that wait_closed() really waits for handlers to complete. + self.start_client("/slow_stop") + server_ws = next(iter(self.server.websockets)) + + # Test that the handler task keeps running after close(). + self.server.close() + self.loop.run_until_complete(asyncio.sleep(MS)) + self.assertFalse(server_ws.handler_task.done()) + + # Test that the handler task terminates before wait_closed() returns. + self.loop.run_until_complete(self.server.wait_closed()) + self.assertTrue(server_ws.handler_task.done()) + + @with_server(create_protocol=ForbiddenServerProtocol) + def test_invalid_status_error_during_client_connect(self): + with self.assertRaises(InvalidStatusCode) as raised: + self.start_client() + exception = raised.exception + self.assertEqual( + str(exception), "server rejected WebSocket connection: HTTP 403" + ) + self.assertEqual(exception.status_code, 403) + + @with_server() + @unittest.mock.patch( + "websockets.server.WebSocketServerProtocol.write_http_response" + ) + @unittest.mock.patch("websockets.server.WebSocketServerProtocol.read_http_request") + def test_connection_error_during_opening_handshake( + self, _read_http_request, _write_http_response + ): + _read_http_request.side_effect = ConnectionError + + # This exception is currently platform-dependent. It was observed to + # be ConnectionResetError on Linux in the non-TLS case, and + # InvalidMessage otherwise (including both Linux and macOS). This + # doesn't matter though since this test is primarily for testing a + # code path on the server side. + with self.assertRaises(Exception): + self.start_client() + + # No response must not be written if the network connection is broken. + _write_http_response.assert_not_called() + + @with_server() + @unittest.mock.patch("websockets.server.WebSocketServerProtocol.close") + def test_connection_error_during_closing_handshake(self, close): + close.side_effect = ConnectionError + + with self.temp_client(): + self.loop.run_until_complete(self.client.send("Hello!")) + reply = self.loop.run_until_complete(self.client.recv()) + self.assertEqual(reply, "Hello!") + + # Connection ends with an abnormal closure. + self.assertEqual(self.client.close_code, 1006) + + +class ClientServerTests( + CommonClientServerTests, ClientServerTestsMixin, AsyncioTestCase +): + pass + + +class SecureClientServerTests( + CommonClientServerTests, SecureClientServerTestsMixin, AsyncioTestCase +): + + # TLS over Unix sockets doesn't make sense. + test_unix_socket = None + + @with_server() + def test_ws_uri_is_rejected(self): + with self.assertRaises(ValueError): + connect(get_server_uri(self.server, secure=False), ssl=self.client_context) + + @with_server() + def test_redirect_insecure(self): + with temp_test_redirecting_server( + self, http.HTTPStatus.FOUND, force_insecure=True + ): + with self.assertRaises(InvalidHandshake): + with temp_test_client(self): + self.fail("Did not raise") # pragma: no cover + + +class ClientServerOriginTests(AsyncioTestCase): + def test_checking_origin_succeeds(self): + server = self.loop.run_until_complete( + serve(handler, "localhost", 0, origins=["http://localhost"]) + ) + client = self.loop.run_until_complete( + connect(get_server_uri(server), origin="http://localhost") + ) + + self.loop.run_until_complete(client.send("Hello!")) + self.assertEqual(self.loop.run_until_complete(client.recv()), "Hello!") + + self.loop.run_until_complete(client.close()) + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_checking_origin_fails(self): + server = self.loop.run_until_complete( + serve(handler, "localhost", 0, origins=["http://localhost"]) + ) + with self.assertRaisesRegex( + InvalidHandshake, "server rejected WebSocket connection: HTTP 403" + ): + self.loop.run_until_complete( + connect(get_server_uri(server), origin="http://otherhost") + ) + + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_checking_origins_fails_with_multiple_headers(self): + server = self.loop.run_until_complete( + serve(handler, "localhost", 0, origins=["http://localhost"]) + ) + with self.assertRaisesRegex( + InvalidHandshake, "server rejected WebSocket connection: HTTP 400" + ): + self.loop.run_until_complete( + connect( + get_server_uri(server), + origin="http://localhost", + extra_headers=[("Origin", "http://otherhost")], + ) + ) + + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_checking_lack_of_origin_succeeds(self): + server = self.loop.run_until_complete( + serve(handler, "localhost", 0, origins=[None]) + ) + client = self.loop.run_until_complete(connect(get_server_uri(server))) + + self.loop.run_until_complete(client.send("Hello!")) + self.assertEqual(self.loop.run_until_complete(client.recv()), "Hello!") + + self.loop.run_until_complete(client.close()) + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_checking_lack_of_origin_succeeds_backwards_compatibility(self): + with warnings.catch_warnings(record=True) as recorded_warnings: + server = self.loop.run_until_complete( + serve(handler, "localhost", 0, origins=[""]) + ) + client = self.loop.run_until_complete(connect(get_server_uri(server))) + + self.assertDeprecationWarnings( + recorded_warnings, ["use None instead of '' in origins"] + ) + + self.loop.run_until_complete(client.send("Hello!")) + self.assertEqual(self.loop.run_until_complete(client.recv()), "Hello!") + + self.loop.run_until_complete(client.close()) + server.close() + self.loop.run_until_complete(server.wait_closed()) + + +class YieldFromTests(AsyncioTestCase): + def test_client(self): + start_server = serve(handler, "localhost", 0) + server = self.loop.run_until_complete(start_server) + + # @asyncio.coroutine is deprecated on Python ≥ 3.8 + with warnings.catch_warnings(record=True): + + @asyncio.coroutine + def run_client(): + # Yield from connect. + client = yield from connect(get_server_uri(server)) + self.assertEqual(client.state, State.OPEN) + yield from client.close() + self.assertEqual(client.state, State.CLOSED) + + self.loop.run_until_complete(run_client()) + + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_server(self): + # @asyncio.coroutine is deprecated on Python ≥ 3.8 + with warnings.catch_warnings(record=True): + + @asyncio.coroutine + def run_server(): + # Yield from serve. + server = yield from serve(handler, "localhost", 0) + self.assertTrue(server.sockets) + server.close() + yield from server.wait_closed() + self.assertFalse(server.sockets) + + self.loop.run_until_complete(run_server()) + + +class AsyncAwaitTests(AsyncioTestCase): + def test_client(self): + start_server = serve(handler, "localhost", 0) + server = self.loop.run_until_complete(start_server) + + async def run_client(): + # Await connect. + client = await connect(get_server_uri(server)) + self.assertEqual(client.state, State.OPEN) + await client.close() + self.assertEqual(client.state, State.CLOSED) + + self.loop.run_until_complete(run_client()) + + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_server(self): + async def run_server(): + # Await serve. + server = await serve(handler, "localhost", 0) + self.assertTrue(server.sockets) + server.close() + await server.wait_closed() + self.assertFalse(server.sockets) + + self.loop.run_until_complete(run_server()) + + +class ContextManagerTests(AsyncioTestCase): + def test_client(self): + start_server = serve(handler, "localhost", 0) + server = self.loop.run_until_complete(start_server) + + async def run_client(): + # Use connect as an asynchronous context manager. + async with connect(get_server_uri(server)) as client: + self.assertEqual(client.state, State.OPEN) + + # Check that exiting the context manager closed the connection. + self.assertEqual(client.state, State.CLOSED) + + self.loop.run_until_complete(run_client()) + + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_server(self): + async def run_server(): + # Use serve as an asynchronous context manager. + async with serve(handler, "localhost", 0) as server: + self.assertTrue(server.sockets) + + # Check that exiting the context manager closed the server. + self.assertFalse(server.sockets) + + self.loop.run_until_complete(run_server()) + + @unittest.skipUnless(hasattr(socket, "AF_UNIX"), "this test requires Unix sockets") + def test_unix_server(self): + async def run_server(path): + async with unix_serve(handler, path) as server: + self.assertTrue(server.sockets) + + # Check that exiting the context manager closed the server. + self.assertFalse(server.sockets) + + with tempfile.TemporaryDirectory() as temp_dir: + path = bytes(pathlib.Path(temp_dir) / "websockets") + self.loop.run_until_complete(run_server(path)) + + +class AsyncIteratorTests(AsyncioTestCase): + + # This is a protocol-level feature, but since it's a high-level API, it is + # much easier to exercise at the client or server level. + + MESSAGES = ["3", "2", "1", "Fire!"] + + def test_iterate_on_messages(self): + async def handler(ws, path): + for message in self.MESSAGES: + await ws.send(message) + + start_server = serve(handler, "localhost", 0) + server = self.loop.run_until_complete(start_server) + + messages = [] + + async def run_client(): + nonlocal messages + async with connect(get_server_uri(server)) as ws: + async for message in ws: + messages.append(message) + + self.loop.run_until_complete(run_client()) + + self.assertEqual(messages, self.MESSAGES) + + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_iterate_on_messages_going_away_exit_ok(self): + async def handler(ws, path): + for message in self.MESSAGES: + await ws.send(message) + await ws.close(1001) + + start_server = serve(handler, "localhost", 0) + server = self.loop.run_until_complete(start_server) + + messages = [] + + async def run_client(): + nonlocal messages + async with connect(get_server_uri(server)) as ws: + async for message in ws: + messages.append(message) + + self.loop.run_until_complete(run_client()) + + self.assertEqual(messages, self.MESSAGES) + + server.close() + self.loop.run_until_complete(server.wait_closed()) + + def test_iterate_on_messages_internal_error_exit_not_ok(self): + async def handler(ws, path): + for message in self.MESSAGES: + await ws.send(message) + await ws.close(1011) + + start_server = serve(handler, "localhost", 0) + server = self.loop.run_until_complete(start_server) + + messages = [] + + async def run_client(): + nonlocal messages + async with connect(get_server_uri(server)) as ws: + async for message in ws: + messages.append(message) + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(run_client()) + + self.assertEqual(messages, self.MESSAGES) + + server.close() + self.loop.run_until_complete(server.wait_closed()) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_exceptions.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_exceptions.py new file mode 100644 index 00000000000..7ad5ad83359 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_exceptions.py @@ -0,0 +1,145 @@ +import unittest + +from websockets.exceptions import * +from websockets.http import Headers + + +class ExceptionsTests(unittest.TestCase): + def test_str(self): + for exception, exception_str in [ + # fmt: off + ( + WebSocketException("something went wrong"), + "something went wrong", + ), + ( + ConnectionClosed(1000, ""), + "code = 1000 (OK), no reason", + ), + ( + ConnectionClosed(1006, None), + "code = 1006 (connection closed abnormally [internal]), no reason" + ), + ( + ConnectionClosed(3000, None), + "code = 3000 (registered), no reason" + ), + ( + ConnectionClosed(4000, None), + "code = 4000 (private use), no reason" + ), + ( + ConnectionClosedError(1016, None), + "code = 1016 (unknown), no reason" + ), + ( + ConnectionClosedOK(1001, "bye"), + "code = 1001 (going away), reason = bye", + ), + ( + InvalidHandshake("invalid request"), + "invalid request", + ), + ( + SecurityError("redirect from WSS to WS"), + "redirect from WSS to WS", + ), + ( + InvalidMessage("malformed HTTP message"), + "malformed HTTP message", + ), + ( + InvalidHeader("Name"), + "missing Name header", + ), + ( + InvalidHeader("Name", None), + "missing Name header", + ), + ( + InvalidHeader("Name", ""), + "empty Name header", + ), + ( + InvalidHeader("Name", "Value"), + "invalid Name header: Value", + ), + ( + InvalidHeaderFormat( + "Sec-WebSocket-Protocol", "expected token", "a=|", 3 + ), + "invalid Sec-WebSocket-Protocol header: " + "expected token at 3 in a=|", + ), + ( + InvalidHeaderValue("Sec-WebSocket-Version", "42"), + "invalid Sec-WebSocket-Version header: 42", + ), + ( + InvalidOrigin("http://bad.origin"), + "invalid Origin header: http://bad.origin", + ), + ( + InvalidUpgrade("Upgrade"), + "missing Upgrade header", + ), + ( + InvalidUpgrade("Connection", "websocket"), + "invalid Connection header: websocket", + ), + ( + InvalidStatusCode(403), + "server rejected WebSocket connection: HTTP 403", + ), + ( + NegotiationError("unsupported subprotocol: spam"), + "unsupported subprotocol: spam", + ), + ( + DuplicateParameter("a"), + "duplicate parameter: a", + ), + ( + InvalidParameterName("|"), + "invalid parameter name: |", + ), + ( + InvalidParameterValue("a", None), + "missing value for parameter a", + ), + ( + InvalidParameterValue("a", ""), + "empty value for parameter a", + ), + ( + InvalidParameterValue("a", "|"), + "invalid value for parameter a: |", + ), + ( + AbortHandshake(200, Headers(), b"OK\n"), + "HTTP 200, 0 headers, 3 bytes", + ), + ( + RedirectHandshake("wss://example.com"), + "redirect to wss://example.com", + ), + ( + InvalidState("WebSocket connection isn't established yet"), + "WebSocket connection isn't established yet", + ), + ( + InvalidURI("|"), + "| isn't a valid URI", + ), + ( + PayloadTooBig("payload length exceeds limit: 2 > 1 bytes"), + "payload length exceeds limit: 2 > 1 bytes", + ), + ( + ProtocolError("invalid opcode: 7"), + "invalid opcode: 7", + ), + # fmt: on + ]: + with self.subTest(exception=exception): + self.assertEqual(str(exception), exception_str) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_exports.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_exports.py new file mode 100644 index 00000000000..7fcbc80e389 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_exports.py @@ -0,0 +1,22 @@ +import unittest + +import websockets + + +combined_exports = ( + websockets.auth.__all__ + + websockets.client.__all__ + + websockets.exceptions.__all__ + + websockets.protocol.__all__ + + websockets.server.__all__ + + websockets.typing.__all__ + + websockets.uri.__all__ +) + + +class TestExportsAllSubmodules(unittest.TestCase): + def test_top_level_module_reexports_all_submodule_exports(self): + self.assertEqual(set(combined_exports), set(websockets.__all__)) + + def test_submodule_exports_are_globally_unique(self): + self.assertEqual(len(set(combined_exports)), len(combined_exports)) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_framing.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_framing.py new file mode 100644 index 00000000000..5def415d289 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_framing.py @@ -0,0 +1,242 @@ +import asyncio +import codecs +import unittest +import unittest.mock + +from websockets.exceptions import PayloadTooBig, ProtocolError +from websockets.framing import * + +from .utils import AsyncioTestCase + + +class FramingTests(AsyncioTestCase): + def decode(self, message, mask=False, max_size=None, extensions=None): + self.stream = asyncio.StreamReader(loop=self.loop) + self.stream.feed_data(message) + self.stream.feed_eof() + frame = self.loop.run_until_complete( + Frame.read( + self.stream.readexactly, + mask=mask, + max_size=max_size, + extensions=extensions, + ) + ) + # Make sure all the data was consumed. + self.assertTrue(self.stream.at_eof()) + return frame + + def encode(self, frame, mask=False, extensions=None): + write = unittest.mock.Mock() + frame.write(write, mask=mask, extensions=extensions) + # Ensure the entire frame is sent with a single call to write(). + # Multiple calls cause TCP fragmentation and degrade performance. + self.assertEqual(write.call_count, 1) + # The frame data is the single positional argument of that call. + self.assertEqual(len(write.call_args[0]), 1) + self.assertEqual(len(write.call_args[1]), 0) + return write.call_args[0][0] + + def round_trip(self, message, expected, mask=False, extensions=None): + decoded = self.decode(message, mask, extensions=extensions) + self.assertEqual(decoded, expected) + encoded = self.encode(decoded, mask, extensions=extensions) + if mask: # non-deterministic encoding + decoded = self.decode(encoded, mask, extensions=extensions) + self.assertEqual(decoded, expected) + else: # deterministic encoding + self.assertEqual(encoded, message) + + def round_trip_close(self, data, code, reason): + parsed = parse_close(data) + self.assertEqual(parsed, (code, reason)) + serialized = serialize_close(code, reason) + self.assertEqual(serialized, data) + + def test_text(self): + self.round_trip(b"\x81\x04Spam", Frame(True, OP_TEXT, b"Spam")) + + def test_text_masked(self): + self.round_trip( + b"\x81\x84\x5b\xfb\xe1\xa8\x08\x8b\x80\xc5", + Frame(True, OP_TEXT, b"Spam"), + mask=True, + ) + + def test_binary(self): + self.round_trip(b"\x82\x04Eggs", Frame(True, OP_BINARY, b"Eggs")) + + def test_binary_masked(self): + self.round_trip( + b"\x82\x84\x53\xcd\xe2\x89\x16\xaa\x85\xfa", + Frame(True, OP_BINARY, b"Eggs"), + mask=True, + ) + + def test_non_ascii_text(self): + self.round_trip( + b"\x81\x05caf\xc3\xa9", Frame(True, OP_TEXT, "café".encode("utf-8")) + ) + + def test_non_ascii_text_masked(self): + self.round_trip( + b"\x81\x85\x64\xbe\xee\x7e\x07\xdf\x88\xbd\xcd", + Frame(True, OP_TEXT, "café".encode("utf-8")), + mask=True, + ) + + def test_close(self): + self.round_trip(b"\x88\x00", Frame(True, OP_CLOSE, b"")) + + def test_ping(self): + self.round_trip(b"\x89\x04ping", Frame(True, OP_PING, b"ping")) + + def test_pong(self): + self.round_trip(b"\x8a\x04pong", Frame(True, OP_PONG, b"pong")) + + def test_long(self): + self.round_trip( + b"\x82\x7e\x00\x7e" + 126 * b"a", Frame(True, OP_BINARY, 126 * b"a") + ) + + def test_very_long(self): + self.round_trip( + b"\x82\x7f\x00\x00\x00\x00\x00\x01\x00\x00" + 65536 * b"a", + Frame(True, OP_BINARY, 65536 * b"a"), + ) + + def test_payload_too_big(self): + with self.assertRaises(PayloadTooBig): + self.decode(b"\x82\x7e\x04\x01" + 1025 * b"a", max_size=1024) + + def test_bad_reserved_bits(self): + for encoded in [b"\xc0\x00", b"\xa0\x00", b"\x90\x00"]: + with self.subTest(encoded=encoded): + with self.assertRaises(ProtocolError): + self.decode(encoded) + + def test_good_opcode(self): + for opcode in list(range(0x00, 0x03)) + list(range(0x08, 0x0B)): + encoded = bytes([0x80 | opcode, 0]) + with self.subTest(encoded=encoded): + self.decode(encoded) # does not raise an exception + + def test_bad_opcode(self): + for opcode in list(range(0x03, 0x08)) + list(range(0x0B, 0x10)): + encoded = bytes([0x80 | opcode, 0]) + with self.subTest(encoded=encoded): + with self.assertRaises(ProtocolError): + self.decode(encoded) + + def test_mask_flag(self): + # Mask flag correctly set. + self.decode(b"\x80\x80\x00\x00\x00\x00", mask=True) + # Mask flag incorrectly unset. + with self.assertRaises(ProtocolError): + self.decode(b"\x80\x80\x00\x00\x00\x00") + # Mask flag correctly unset. + self.decode(b"\x80\x00") + # Mask flag incorrectly set. + with self.assertRaises(ProtocolError): + self.decode(b"\x80\x00", mask=True) + + def test_control_frame_max_length(self): + # At maximum allowed length. + self.decode(b"\x88\x7e\x00\x7d" + 125 * b"a") + # Above maximum allowed length. + with self.assertRaises(ProtocolError): + self.decode(b"\x88\x7e\x00\x7e" + 126 * b"a") + + def test_prepare_data_str(self): + self.assertEqual(prepare_data("café"), (OP_TEXT, b"caf\xc3\xa9")) + + def test_prepare_data_bytes(self): + self.assertEqual(prepare_data(b"tea"), (OP_BINARY, b"tea")) + + def test_prepare_data_bytearray(self): + self.assertEqual( + prepare_data(bytearray(b"tea")), (OP_BINARY, bytearray(b"tea")) + ) + + def test_prepare_data_memoryview(self): + self.assertEqual( + prepare_data(memoryview(b"tea")), (OP_BINARY, memoryview(b"tea")) + ) + + def test_prepare_data_non_contiguous_memoryview(self): + self.assertEqual(prepare_data(memoryview(b"tteeaa")[::2]), (OP_BINARY, b"tea")) + + def test_prepare_data_list(self): + with self.assertRaises(TypeError): + prepare_data([]) + + def test_prepare_data_none(self): + with self.assertRaises(TypeError): + prepare_data(None) + + def test_encode_data_str(self): + self.assertEqual(encode_data("café"), b"caf\xc3\xa9") + + def test_encode_data_bytes(self): + self.assertEqual(encode_data(b"tea"), b"tea") + + def test_encode_data_bytearray(self): + self.assertEqual(encode_data(bytearray(b"tea")), b"tea") + + def test_encode_data_memoryview(self): + self.assertEqual(encode_data(memoryview(b"tea")), b"tea") + + def test_encode_data_non_contiguous_memoryview(self): + self.assertEqual(encode_data(memoryview(b"tteeaa")[::2]), b"tea") + + def test_encode_data_list(self): + with self.assertRaises(TypeError): + encode_data([]) + + def test_encode_data_none(self): + with self.assertRaises(TypeError): + encode_data(None) + + def test_fragmented_control_frame(self): + # Fin bit correctly set. + self.decode(b"\x88\x00") + # Fin bit incorrectly unset. + with self.assertRaises(ProtocolError): + self.decode(b"\x08\x00") + + def test_parse_close_and_serialize_close(self): + self.round_trip_close(b"\x03\xe8", 1000, "") + self.round_trip_close(b"\x03\xe8OK", 1000, "OK") + + def test_parse_close_empty(self): + self.assertEqual(parse_close(b""), (1005, "")) + + def test_parse_close_errors(self): + with self.assertRaises(ProtocolError): + parse_close(b"\x03") + with self.assertRaises(ProtocolError): + parse_close(b"\x03\xe7") + with self.assertRaises(UnicodeDecodeError): + parse_close(b"\x03\xe8\xff\xff") + + def test_serialize_close_errors(self): + with self.assertRaises(ProtocolError): + serialize_close(999, "") + + def test_extensions(self): + class Rot13: + @staticmethod + def encode(frame): + assert frame.opcode == OP_TEXT + text = frame.data.decode() + data = codecs.encode(text, "rot13").encode() + return frame._replace(data=data) + + # This extensions is symmetrical. + @staticmethod + def decode(frame, *, max_size=None): + return Rot13.encode(frame) + + self.round_trip( + b"\x81\x05uryyb", Frame(True, OP_TEXT, b"hello"), extensions=[Rot13()] + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_handshake.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_handshake.py new file mode 100644 index 00000000000..7d04777152b --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_handshake.py @@ -0,0 +1,190 @@ +import contextlib +import unittest + +from websockets.exceptions import ( + InvalidHandshake, + InvalidHeader, + InvalidHeaderValue, + InvalidUpgrade, +) +from websockets.handshake import * +from websockets.handshake import accept # private API +from websockets.http import Headers + + +class HandshakeTests(unittest.TestCase): + def test_accept(self): + # Test vector from RFC 6455 + key = "dGhlIHNhbXBsZSBub25jZQ==" + acc = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" + self.assertEqual(accept(key), acc) + + def test_round_trip(self): + request_headers = Headers() + request_key = build_request(request_headers) + response_key = check_request(request_headers) + self.assertEqual(request_key, response_key) + response_headers = Headers() + build_response(response_headers, response_key) + check_response(response_headers, request_key) + + @contextlib.contextmanager + def assertValidRequestHeaders(self): + """ + Provide request headers for modification. + + Assert that the transformation kept them valid. + + """ + headers = Headers() + build_request(headers) + yield headers + check_request(headers) + + @contextlib.contextmanager + def assertInvalidRequestHeaders(self, exc_type): + """ + Provide request headers for modification. + + Assert that the transformation made them invalid. + + """ + headers = Headers() + build_request(headers) + yield headers + assert issubclass(exc_type, InvalidHandshake) + with self.assertRaises(exc_type): + check_request(headers) + + def test_request_invalid_connection(self): + with self.assertInvalidRequestHeaders(InvalidUpgrade) as headers: + del headers["Connection"] + headers["Connection"] = "Downgrade" + + def test_request_missing_connection(self): + with self.assertInvalidRequestHeaders(InvalidUpgrade) as headers: + del headers["Connection"] + + def test_request_additional_connection(self): + with self.assertValidRequestHeaders() as headers: + headers["Connection"] = "close" + + def test_request_invalid_upgrade(self): + with self.assertInvalidRequestHeaders(InvalidUpgrade) as headers: + del headers["Upgrade"] + headers["Upgrade"] = "socketweb" + + def test_request_missing_upgrade(self): + with self.assertInvalidRequestHeaders(InvalidUpgrade) as headers: + del headers["Upgrade"] + + def test_request_additional_upgrade(self): + with self.assertInvalidRequestHeaders(InvalidUpgrade) as headers: + headers["Upgrade"] = "socketweb" + + def test_request_invalid_key_not_base64(self): + with self.assertInvalidRequestHeaders(InvalidHeaderValue) as headers: + del headers["Sec-WebSocket-Key"] + headers["Sec-WebSocket-Key"] = "!@#$%^&*()" + + def test_request_invalid_key_not_well_padded(self): + with self.assertInvalidRequestHeaders(InvalidHeaderValue) as headers: + del headers["Sec-WebSocket-Key"] + headers["Sec-WebSocket-Key"] = "CSIRmL8dWYxeAdr/XpEHRw" + + def test_request_invalid_key_not_16_bytes_long(self): + with self.assertInvalidRequestHeaders(InvalidHeaderValue) as headers: + del headers["Sec-WebSocket-Key"] + headers["Sec-WebSocket-Key"] = "ZLpprpvK4PE=" + + def test_request_missing_key(self): + with self.assertInvalidRequestHeaders(InvalidHeader) as headers: + del headers["Sec-WebSocket-Key"] + + def test_request_additional_key(self): + with self.assertInvalidRequestHeaders(InvalidHeader) as headers: + # This duplicates the Sec-WebSocket-Key header. + headers["Sec-WebSocket-Key"] = headers["Sec-WebSocket-Key"] + + def test_request_invalid_version(self): + with self.assertInvalidRequestHeaders(InvalidHeaderValue) as headers: + del headers["Sec-WebSocket-Version"] + headers["Sec-WebSocket-Version"] = "42" + + def test_request_missing_version(self): + with self.assertInvalidRequestHeaders(InvalidHeader) as headers: + del headers["Sec-WebSocket-Version"] + + def test_request_additional_version(self): + with self.assertInvalidRequestHeaders(InvalidHeader) as headers: + # This duplicates the Sec-WebSocket-Version header. + headers["Sec-WebSocket-Version"] = headers["Sec-WebSocket-Version"] + + @contextlib.contextmanager + def assertValidResponseHeaders(self, key="CSIRmL8dWYxeAdr/XpEHRw=="): + """ + Provide response headers for modification. + + Assert that the transformation kept them valid. + + """ + headers = Headers() + build_response(headers, key) + yield headers + check_response(headers, key) + + @contextlib.contextmanager + def assertInvalidResponseHeaders(self, exc_type, key="CSIRmL8dWYxeAdr/XpEHRw=="): + """ + Provide response headers for modification. + + Assert that the transformation made them invalid. + + """ + headers = Headers() + build_response(headers, key) + yield headers + assert issubclass(exc_type, InvalidHandshake) + with self.assertRaises(exc_type): + check_response(headers, key) + + def test_response_invalid_connection(self): + with self.assertInvalidResponseHeaders(InvalidUpgrade) as headers: + del headers["Connection"] + headers["Connection"] = "Downgrade" + + def test_response_missing_connection(self): + with self.assertInvalidResponseHeaders(InvalidUpgrade) as headers: + del headers["Connection"] + + def test_response_additional_connection(self): + with self.assertValidResponseHeaders() as headers: + headers["Connection"] = "close" + + def test_response_invalid_upgrade(self): + with self.assertInvalidResponseHeaders(InvalidUpgrade) as headers: + del headers["Upgrade"] + headers["Upgrade"] = "socketweb" + + def test_response_missing_upgrade(self): + with self.assertInvalidResponseHeaders(InvalidUpgrade) as headers: + del headers["Upgrade"] + + def test_response_additional_upgrade(self): + with self.assertInvalidResponseHeaders(InvalidUpgrade) as headers: + headers["Upgrade"] = "socketweb" + + def test_response_invalid_accept(self): + with self.assertInvalidResponseHeaders(InvalidHeaderValue) as headers: + del headers["Sec-WebSocket-Accept"] + other_key = "1Eq4UDEFQYg3YspNgqxv5g==" + headers["Sec-WebSocket-Accept"] = accept(other_key) + + def test_response_missing_accept(self): + with self.assertInvalidResponseHeaders(InvalidHeader) as headers: + del headers["Sec-WebSocket-Accept"] + + def test_response_additional_accept(self): + with self.assertInvalidResponseHeaders(InvalidHeader) as headers: + # This duplicates the Sec-WebSocket-Accept header. + headers["Sec-WebSocket-Accept"] = headers["Sec-WebSocket-Accept"] diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_headers.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_headers.py new file mode 100644 index 00000000000..26d85fa5eaa --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_headers.py @@ -0,0 +1,185 @@ +import unittest + +from websockets.exceptions import InvalidHeaderFormat, InvalidHeaderValue +from websockets.headers import * + + +class HeadersTests(unittest.TestCase): + def test_parse_connection(self): + for header, parsed in [ + # Realistic use cases + ("Upgrade", ["Upgrade"]), # Safari, Chrome + ("keep-alive, Upgrade", ["keep-alive", "Upgrade"]), # Firefox + # Pathological example + (",,\t, , ,Upgrade ,,", ["Upgrade"]), + ]: + with self.subTest(header=header): + self.assertEqual(parse_connection(header), parsed) + + def test_parse_connection_invalid_header_format(self): + for header in ["???", "keep-alive; Upgrade"]: + with self.subTest(header=header): + with self.assertRaises(InvalidHeaderFormat): + parse_connection(header) + + def test_parse_upgrade(self): + for header, parsed in [ + # Realistic use case + ("websocket", ["websocket"]), + # Synthetic example + ("http/3.0, websocket", ["http/3.0", "websocket"]), + # Pathological example + (",, WebSocket, \t,,", ["WebSocket"]), + ]: + with self.subTest(header=header): + self.assertEqual(parse_upgrade(header), parsed) + + def test_parse_upgrade_invalid_header_format(self): + for header in ["???", "websocket 2", "http/3.0; websocket"]: + with self.subTest(header=header): + with self.assertRaises(InvalidHeaderFormat): + parse_upgrade(header) + + def test_parse_extension(self): + for header, parsed in [ + # Synthetic examples + ("foo", [("foo", [])]), + ("foo, bar", [("foo", []), ("bar", [])]), + ( + 'foo; name; token=token; quoted-string="quoted-string", ' + "bar; quux; quuux", + [ + ( + "foo", + [ + ("name", None), + ("token", "token"), + ("quoted-string", "quoted-string"), + ], + ), + ("bar", [("quux", None), ("quuux", None)]), + ], + ), + # Pathological example + ( + ",\t, , ,foo ;bar = 42,, baz,,", + [("foo", [("bar", "42")]), ("baz", [])], + ), + # Realistic use cases for permessage-deflate + ("permessage-deflate", [("permessage-deflate", [])]), + ( + "permessage-deflate; client_max_window_bits", + [("permessage-deflate", [("client_max_window_bits", None)])], + ), + ( + "permessage-deflate; server_max_window_bits=10", + [("permessage-deflate", [("server_max_window_bits", "10")])], + ), + ]: + with self.subTest(header=header): + self.assertEqual(parse_extension(header), parsed) + # Also ensure that build_extension round-trips cleanly. + unparsed = build_extension(parsed) + self.assertEqual(parse_extension(unparsed), parsed) + + def test_parse_extension_invalid_header_format(self): + for header in [ + # Truncated examples + "", + ",\t,", + "foo;", + "foo; bar;", + "foo; bar=", + 'foo; bar="baz', + # Wrong delimiter + "foo, bar, baz=quux; quuux", + # Value in quoted string parameter that isn't a token + 'foo; bar=" "', + ]: + with self.subTest(header=header): + with self.assertRaises(InvalidHeaderFormat): + parse_extension(header) + + def test_parse_subprotocol(self): + for header, parsed in [ + # Synthetic examples + ("foo", ["foo"]), + ("foo, bar", ["foo", "bar"]), + # Pathological example + (",\t, , ,foo ,, bar,baz,,", ["foo", "bar", "baz"]), + ]: + with self.subTest(header=header): + self.assertEqual(parse_subprotocol(header), parsed) + # Also ensure that build_subprotocol round-trips cleanly. + unparsed = build_subprotocol(parsed) + self.assertEqual(parse_subprotocol(unparsed), parsed) + + def test_parse_subprotocol_invalid_header(self): + for header in [ + # Truncated examples + "", + ",\t," + # Wrong delimiter + "foo; bar", + ]: + with self.subTest(header=header): + with self.assertRaises(InvalidHeaderFormat): + parse_subprotocol(header) + + def test_build_www_authenticate_basic(self): + # Test vector from RFC 7617 + self.assertEqual( + build_www_authenticate_basic("foo"), 'Basic realm="foo", charset="UTF-8"' + ) + + def test_build_www_authenticate_basic_invalid_realm(self): + # Realm contains a control character forbidden in quoted-string encoding + with self.assertRaises(ValueError): + build_www_authenticate_basic("\u0007") + + def test_build_authorization_basic(self): + # Test vector from RFC 7617 + self.assertEqual( + build_authorization_basic("Aladdin", "open sesame"), + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + ) + + def test_build_authorization_basic_utf8(self): + # Test vector from RFC 7617 + self.assertEqual( + build_authorization_basic("test", "123£"), "Basic dGVzdDoxMjPCow==" + ) + + def test_parse_authorization_basic(self): + for header, parsed in [ + ("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", ("Aladdin", "open sesame")), + # Password contains non-ASCII character + ("Basic dGVzdDoxMjPCow==", ("test", "123£")), + # Password contains a colon + ("Basic YWxhZGRpbjpvcGVuOnNlc2FtZQ==", ("aladdin", "open:sesame")), + # Scheme name must be case insensitive + ("basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", ("Aladdin", "open sesame")), + ]: + with self.subTest(header=header): + self.assertEqual(parse_authorization_basic(header), parsed) + + def test_parse_authorization_basic_invalid_header_format(self): + for header in [ + "// Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", + "Basic\tQWxhZGRpbjpvcGVuIHNlc2FtZQ==", + "Basic ****************************", + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== //", + ]: + with self.subTest(header=header): + with self.assertRaises(InvalidHeaderFormat): + parse_authorization_basic(header) + + def test_parse_authorization_basic_invalid_header_value(self): + for header in [ + "Digest ...", + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ", + "Basic QWxhZGNlc2FtZQ==", + ]: + with self.subTest(header=header): + with self.assertRaises(InvalidHeaderValue): + parse_authorization_basic(header) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_http.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_http.py new file mode 100644 index 00000000000..41b522c3d57 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_http.py @@ -0,0 +1,249 @@ +import asyncio +import unittest + +from websockets.exceptions import SecurityError +from websockets.http import * +from websockets.http import read_headers + +from .utils import AsyncioTestCase + + +class HTTPAsyncTests(AsyncioTestCase): + def setUp(self): + super().setUp() + self.stream = asyncio.StreamReader(loop=self.loop) + + async def test_read_request(self): + # Example from the protocol overview in RFC 6455 + self.stream.feed_data( + b"GET /chat HTTP/1.1\r\n" + b"Host: server.example.com\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n" + b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + b"Origin: http://example.com\r\n" + b"Sec-WebSocket-Protocol: chat, superchat\r\n" + b"Sec-WebSocket-Version: 13\r\n" + b"\r\n" + ) + path, headers = await read_request(self.stream) + self.assertEqual(path, "/chat") + self.assertEqual(headers["Upgrade"], "websocket") + + async def test_read_request_empty(self): + self.stream.feed_eof() + with self.assertRaisesRegex( + EOFError, "connection closed while reading HTTP request line" + ): + await read_request(self.stream) + + async def test_read_request_invalid_request_line(self): + self.stream.feed_data(b"GET /\r\n\r\n") + with self.assertRaisesRegex(ValueError, "invalid HTTP request line: GET /"): + await read_request(self.stream) + + async def test_read_request_unsupported_method(self): + self.stream.feed_data(b"OPTIONS * HTTP/1.1\r\n\r\n") + with self.assertRaisesRegex(ValueError, "unsupported HTTP method: OPTIONS"): + await read_request(self.stream) + + async def test_read_request_unsupported_version(self): + self.stream.feed_data(b"GET /chat HTTP/1.0\r\n\r\n") + with self.assertRaisesRegex(ValueError, "unsupported HTTP version: HTTP/1.0"): + await read_request(self.stream) + + async def test_read_request_invalid_header(self): + self.stream.feed_data(b"GET /chat HTTP/1.1\r\nOops\r\n") + with self.assertRaisesRegex(ValueError, "invalid HTTP header line: Oops"): + await read_request(self.stream) + + async def test_read_response(self): + # Example from the protocol overview in RFC 6455 + self.stream.feed_data( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: websocket\r\n" + b"Connection: Upgrade\r\n" + b"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + b"Sec-WebSocket-Protocol: chat\r\n" + b"\r\n" + ) + status_code, reason, headers = await read_response(self.stream) + self.assertEqual(status_code, 101) + self.assertEqual(reason, "Switching Protocols") + self.assertEqual(headers["Upgrade"], "websocket") + + async def test_read_response_empty(self): + self.stream.feed_eof() + with self.assertRaisesRegex( + EOFError, "connection closed while reading HTTP status line" + ): + await read_response(self.stream) + + async def test_read_request_invalid_status_line(self): + self.stream.feed_data(b"Hello!\r\n") + with self.assertRaisesRegex(ValueError, "invalid HTTP status line: Hello!"): + await read_response(self.stream) + + async def test_read_response_unsupported_version(self): + self.stream.feed_data(b"HTTP/1.0 400 Bad Request\r\n\r\n") + with self.assertRaisesRegex(ValueError, "unsupported HTTP version: HTTP/1.0"): + await read_response(self.stream) + + async def test_read_response_invalid_status(self): + self.stream.feed_data(b"HTTP/1.1 OMG WTF\r\n\r\n") + with self.assertRaisesRegex(ValueError, "invalid HTTP status code: OMG"): + await read_response(self.stream) + + async def test_read_response_unsupported_status(self): + self.stream.feed_data(b"HTTP/1.1 007 My name is Bond\r\n\r\n") + with self.assertRaisesRegex(ValueError, "unsupported HTTP status code: 007"): + await read_response(self.stream) + + async def test_read_response_invalid_reason(self): + self.stream.feed_data(b"HTTP/1.1 200 \x7f\r\n\r\n") + with self.assertRaisesRegex(ValueError, "invalid HTTP reason phrase: \\x7f"): + await read_response(self.stream) + + async def test_read_response_invalid_header(self): + self.stream.feed_data(b"HTTP/1.1 500 Internal Server Error\r\nOops\r\n") + with self.assertRaisesRegex(ValueError, "invalid HTTP header line: Oops"): + await read_response(self.stream) + + async def test_header_name(self): + self.stream.feed_data(b"foo bar: baz qux\r\n\r\n") + with self.assertRaises(ValueError): + await read_headers(self.stream) + + async def test_header_value(self): + self.stream.feed_data(b"foo: \x00\x00\x0f\r\n\r\n") + with self.assertRaises(ValueError): + await read_headers(self.stream) + + async def test_headers_limit(self): + self.stream.feed_data(b"foo: bar\r\n" * 257 + b"\r\n") + with self.assertRaises(SecurityError): + await read_headers(self.stream) + + async def test_line_limit(self): + # Header line contains 5 + 4090 + 2 = 4097 bytes. + self.stream.feed_data(b"foo: " + b"a" * 4090 + b"\r\n\r\n") + with self.assertRaises(SecurityError): + await read_headers(self.stream) + + async def test_line_ending(self): + self.stream.feed_data(b"foo: bar\n\n") + with self.assertRaises(EOFError): + await read_headers(self.stream) + + +class HeadersTests(unittest.TestCase): + def setUp(self): + self.headers = Headers([("Connection", "Upgrade"), ("Server", USER_AGENT)]) + + def test_str(self): + self.assertEqual( + str(self.headers), f"Connection: Upgrade\r\nServer: {USER_AGENT}\r\n\r\n" + ) + + def test_repr(self): + self.assertEqual( + repr(self.headers), + f"Headers([('Connection', 'Upgrade'), " f"('Server', '{USER_AGENT}')])", + ) + + def test_multiple_values_error_str(self): + self.assertEqual(str(MultipleValuesError("Connection")), "'Connection'") + self.assertEqual(str(MultipleValuesError()), "") + + def test_contains(self): + self.assertIn("Server", self.headers) + + def test_contains_case_insensitive(self): + self.assertIn("server", self.headers) + + def test_contains_not_found(self): + self.assertNotIn("Date", self.headers) + + def test_contains_non_string_key(self): + self.assertNotIn(42, self.headers) + + def test_iter(self): + self.assertEqual(set(iter(self.headers)), {"connection", "server"}) + + def test_len(self): + self.assertEqual(len(self.headers), 2) + + def test_getitem(self): + self.assertEqual(self.headers["Server"], USER_AGENT) + + def test_getitem_case_insensitive(self): + self.assertEqual(self.headers["server"], USER_AGENT) + + def test_getitem_key_error(self): + with self.assertRaises(KeyError): + self.headers["Upgrade"] + + def test_getitem_multiple_values_error(self): + self.headers["Server"] = "2" + with self.assertRaises(MultipleValuesError): + self.headers["Server"] + + def test_setitem(self): + self.headers["Upgrade"] = "websocket" + self.assertEqual(self.headers["Upgrade"], "websocket") + + def test_setitem_case_insensitive(self): + self.headers["upgrade"] = "websocket" + self.assertEqual(self.headers["Upgrade"], "websocket") + + def test_setitem_multiple_values(self): + self.headers["Connection"] = "close" + with self.assertRaises(MultipleValuesError): + self.headers["Connection"] + + def test_delitem(self): + del self.headers["Connection"] + with self.assertRaises(KeyError): + self.headers["Connection"] + + def test_delitem_case_insensitive(self): + del self.headers["connection"] + with self.assertRaises(KeyError): + self.headers["Connection"] + + def test_delitem_multiple_values(self): + self.headers["Connection"] = "close" + del self.headers["Connection"] + with self.assertRaises(KeyError): + self.headers["Connection"] + + def test_eq(self): + other_headers = self.headers.copy() + self.assertEqual(self.headers, other_headers) + + def test_eq_not_equal(self): + self.assertNotEqual(self.headers, []) + + def test_clear(self): + self.headers.clear() + self.assertFalse(self.headers) + self.assertEqual(self.headers, Headers()) + + def test_get_all(self): + self.assertEqual(self.headers.get_all("Connection"), ["Upgrade"]) + + def test_get_all_case_insensitive(self): + self.assertEqual(self.headers.get_all("connection"), ["Upgrade"]) + + def test_get_all_no_values(self): + self.assertEqual(self.headers.get_all("Upgrade"), []) + + def test_get_all_multiple_values(self): + self.headers["Connection"] = "close" + self.assertEqual(self.headers.get_all("Connection"), ["Upgrade", "close"]) + + def test_raw_items(self): + self.assertEqual( + list(self.headers.raw_items()), + [("Connection", "Upgrade"), ("Server", USER_AGENT)], + ) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_localhost.cnf b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_localhost.cnf new file mode 100644 index 00000000000..6dc331ac69a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_localhost.cnf @@ -0,0 +1,26 @@ +[ req ] + +default_md = sha256 +encrypt_key = no + +prompt = no + +distinguished_name = dn +x509_extensions = ext + +[ dn ] + +C = "FR" +L = "Paris" +O = "Aymeric Augustin" +CN = "localhost" + +[ ext ] + +subjectAltName = @san + +[ san ] + +DNS.1 = localhost +IP.2 = 127.0.0.1 +IP.3 = ::1 diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_localhost.pem b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_localhost.pem new file mode 100644 index 00000000000..b8a9ea9ab3a --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_localhost.pem @@ -0,0 +1,48 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCUgrQVkNbAWRlo +zZUj14Ufz7YEp2MXmvmhdlfOGLwjy+xPO98aJRv5/nYF2eWM3llcmLe8FbBSK+QF +To4su7ZVnc6qITOHqcSDUw06WarQUMs94bhHUvQp1u8+b2hNiMeGw6+QiBI6OJRO +iGpLRbkN6Uj3AKwi8SYVoLyMiztuwbNyGf8fF3DDpHZtBitGtMSBCMsQsfB465pl +2UoyBrWa2lsbLt3VvBZZvHqfEuPjpjjKN5USIXnaf0NizaR6ps3EyfftWy4i7zIQ +N5uTExvaPDyPn9nH3q/dkT99mSMSU1AvTTpX8PN7DlqE6wZMbQsBPRGW7GElQ+Ox +IKdKOLk5AgMBAAECggEAd3kqzQqnaTiEs4ZoC9yPUUc1pErQ8iWP27Ar9TZ67MVa +B2ggFJV0C0sFwbFI9WnPNCn77gj4vzJmD0riH+SnS/tXThDFtscBu7BtvNp0C4Bj +8RWMvXxjxuENuQnBPFbkRWtZ6wk8uK/Zx9AAyyt9M07Qjz1wPfAIdm/IH7zHBFMA +gsqjnkLh1r0FvjNEbLiuGqYU/GVxaZYd+xy+JU52IxjHUUL9yD0BPWb+Szar6AM2 +gUpmTX6+BcCZwwZ//DzCoWYZ9JbP8akn6edBeZyuMPqYgLzZkPyQ+hRW46VPPw89 +yg4LR9nzgQiBHlac0laB4NrWa+d9QRRLitl1O3gVAQKBgQDDkptxXu7w9Lpc+HeE +N/pJfpCzUuF7ZC4vatdoDzvfB5Ky6W88Poq+I7bB9m7StXdFAbDyUBxvisjTBMVA +OtYqpAk/rhX8MjSAtjoFe2nH+eEiQriuZmtA5CdKEXS4hNbc/HhEPWhk7Zh8OV5v +y7l4r6l4UHqaN9QyE0vlFdmcmQKBgQDCZZR/trJ2/g2OquaS+Zd2h/3NXw0NBq4z +4OBEWqNa/R35jdK6WlWJH7+tKOacr+xtswLpPeZHGwMdk64/erbYWBuJWAjpH72J +DM9+1H5fFHANWpWTNn94enQxwfzZRvdkxq4IWzGhesptYnHIzoAmaqC3lbn/e3u0 +Flng32hFoQKBgQCF3D4K3hib0lYQtnxPgmUMktWF+A+fflViXTWs4uhu4mcVkFNz +n7clJ5q6reryzAQjtmGfqRedfRex340HRn46V2aBMK2Znd9zzcZu5CbmGnFvGs3/ +iNiWZNNDjike9sV+IkxLIODoW/vH4xhxWrbLFSjg0ezoy5ew4qZK2abF2QKBgQC5 +M5efeQpbjTyTUERtf/aKCZOGZmkDoPq0GCjxVjzNQdqd1z0NJ2TYR/QP36idXIlu +FZ7PYZaS5aw5MGpQtfOe94n8dm++0et7t0WzunRO1yTNxCA+aSxWNquegAcJZa/q +RdKlyWPmSRqzzZdDzWCPuQQ3AyF5wkYfUy/7qjwoIQKBgB2v96BV7+lICviIKzzb +1o3A3VzAX5MGd98uLGjlK4qsBC+s7mk2eQztiNZgbA0W6fhQ5Dz3HcXJ5ppy8Okc +jeAktrNRzz15hvi/XkWdO+VMqiHW4l+sWYukjhCyod1oO1KGHq0LYYvv076syxGw +vRKLq7IJ4WIp1VtfaBlrIogq +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDTTCCAjWgAwIBAgIJAJ6VG2cQlsepMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV +BAYTAkZSMQ4wDAYDVQQHDAVQYXJpczEZMBcGA1UECgwQQXltZXJpYyBBdWd1c3Rp +bjESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTE4MDUwNTE2NTc1NloYDzIwNjAwNTA0 +MTY1NzU2WjBMMQswCQYDVQQGEwJGUjEOMAwGA1UEBwwFUGFyaXMxGTAXBgNVBAoM +EEF5bWVyaWMgQXVndXN0aW4xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJSCtBWQ1sBZGWjNlSPXhR/PtgSnYxea+aF2 +V84YvCPL7E873xolG/n+dgXZ5YzeWVyYt7wVsFIr5AVOjiy7tlWdzqohM4epxINT +DTpZqtBQyz3huEdS9CnW7z5vaE2Ix4bDr5CIEjo4lE6IaktFuQ3pSPcArCLxJhWg +vIyLO27Bs3IZ/x8XcMOkdm0GK0a0xIEIyxCx8HjrmmXZSjIGtZraWxsu3dW8Flm8 +ep8S4+OmOMo3lRIhedp/Q2LNpHqmzcTJ9+1bLiLvMhA3m5MTG9o8PI+f2cfer92R +P32ZIxJTUC9NOlfw83sOWoTrBkxtCwE9EZbsYSVD47Egp0o4uTkCAwEAAaMwMC4w +LAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0G +CSqGSIb3DQEBCwUAA4IBAQA0imKp/rflfbDCCx78NdsR5rt0jKem2t3YPGT6tbeU ++FQz62SEdeD2OHWxpvfPf+6h3iTXJbkakr2R4lP3z7GHUe61lt3So9VHAvgbtPTH +aB1gOdThA83o0fzQtnIv67jCvE9gwPQInViZLEcm2iQEZLj6AuSvBKmluTR7vNRj +8/f2R4LsDfCWGrzk2W+deGRvSow7irS88NQ8BW8S8otgMiBx4D2UlOmQwqr6X+/r +jYIDuMb6GDKRXtBUGDokfE94hjj9u2mrNRwt8y4tqu8ZNa//yLEQ0Ow2kP3QJPLY +941VZpwRi2v/+JvI7OBYlvbOTFwM8nAk79k+Dgviygd9 +-----END CERTIFICATE----- diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_protocol.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_protocol.py new file mode 100644 index 00000000000..d32c1f72e7c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_protocol.py @@ -0,0 +1,1475 @@ +import asyncio +import contextlib +import sys +import unittest +import unittest.mock +import warnings + +from websockets.exceptions import ConnectionClosed, InvalidState +from websockets.framing import * +from websockets.protocol import State, WebSocketCommonProtocol + +from .utils import MS, AsyncioTestCase + + +async def async_iterable(iterable): + for item in iterable: + yield item + + +class TransportMock(unittest.mock.Mock): + """ + Transport mock to control the protocol's inputs and outputs in tests. + + It calls the protocol's connection_made and connection_lost methods like + actual transports. + + It also calls the protocol's connection_open method to bypass the + WebSocket handshake. + + To simulate incoming data, tests call the protocol's data_received and + eof_received methods directly. + + They could also pause_writing and resume_writing to test flow control. + + """ + + # This should happen in __init__ but overriding Mock.__init__ is hard. + def setup_mock(self, loop, protocol): + self.loop = loop + self.protocol = protocol + self._eof = False + self._closing = False + # Simulate a successful TCP handshake. + self.protocol.connection_made(self) + # Simulate a successful WebSocket handshake. + self.protocol.connection_open() + + def can_write_eof(self): + return True + + def write_eof(self): + # When the protocol half-closes the TCP connection, it expects the + # other end to close it. Simulate that. + if not self._eof: + self.loop.call_soon(self.close) + self._eof = True + + def close(self): + # Simulate how actual transports drop the connection. + if not self._closing: + self.loop.call_soon(self.protocol.connection_lost, None) + self._closing = True + + def abort(self): + # Change this to an `if` if tests call abort() multiple times. + assert self.protocol.state is not State.CLOSED + self.loop.call_soon(self.protocol.connection_lost, None) + + +class CommonTests: + """ + Mixin that defines most tests but doesn't inherit unittest.TestCase. + + Tests are run by the ServerTests and ClientTests subclasses. + + """ + + def setUp(self): + super().setUp() + # Disable pings to make it easier to test what frames are sent exactly. + self.protocol = WebSocketCommonProtocol(ping_interval=None) + self.transport = TransportMock() + self.transport.setup_mock(self.loop, self.protocol) + + def tearDown(self): + self.transport.close() + self.loop.run_until_complete(self.protocol.close()) + super().tearDown() + + # Utilities for writing tests. + + def make_drain_slow(self, delay=MS): + # Process connection_made in order to initialize self.protocol.transport. + self.run_loop_once() + + original_drain = self.protocol._drain + + async def delayed_drain(): + await asyncio.sleep( + delay, loop=self.loop if sys.version_info[:2] < (3, 8) else None + ) + await original_drain() + + self.protocol._drain = delayed_drain + + close_frame = Frame(True, OP_CLOSE, serialize_close(1000, "close")) + local_close = Frame(True, OP_CLOSE, serialize_close(1000, "local")) + remote_close = Frame(True, OP_CLOSE, serialize_close(1000, "remote")) + + def receive_frame(self, frame): + """ + Make the protocol receive a frame. + + """ + write = self.protocol.data_received + mask = not self.protocol.is_client + frame.write(write, mask=mask) + + def receive_eof(self): + """ + Make the protocol receive the end of the data stream. + + Since ``WebSocketCommonProtocol.eof_received`` returns ``None``, an + actual transport would close itself after calling it. This function + emulates that behavior. + + """ + self.protocol.eof_received() + self.loop.call_soon(self.transport.close) + + def receive_eof_if_client(self): + """ + Like receive_eof, but only if this is the client side. + + Since the server is supposed to initiate the termination of the TCP + connection, this method helps making tests work for both sides. + + """ + if self.protocol.is_client: + self.receive_eof() + + def close_connection(self, code=1000, reason="close"): + """ + Execute a closing handshake. + + This puts the connection in the CLOSED state. + + """ + close_frame_data = serialize_close(code, reason) + # Prepare the response to the closing handshake from the remote side. + self.receive_frame(Frame(True, OP_CLOSE, close_frame_data)) + self.receive_eof_if_client() + # Trigger the closing handshake from the local side and complete it. + self.loop.run_until_complete(self.protocol.close(code, reason)) + # Empty the outgoing data stream so we can make assertions later on. + self.assertOneFrameSent(True, OP_CLOSE, close_frame_data) + + assert self.protocol.state is State.CLOSED + + def half_close_connection_local(self, code=1000, reason="close"): + """ + Start a closing handshake but do not complete it. + + The main difference with `close_connection` is that the connection is + left in the CLOSING state until the event loop runs again. + + The current implementation returns a task that must be awaited or + canceled, else asyncio complains about destroying a pending task. + + """ + close_frame_data = serialize_close(code, reason) + # Trigger the closing handshake from the local endpoint. + close_task = self.loop.create_task(self.protocol.close(code, reason)) + self.run_loop_once() # wait_for executes + self.run_loop_once() # write_frame executes + # Empty the outgoing data stream so we can make assertions later on. + self.assertOneFrameSent(True, OP_CLOSE, close_frame_data) + + assert self.protocol.state is State.CLOSING + + # Complete the closing sequence at 1ms intervals so the test can run + # at each point even it goes back to the event loop several times. + self.loop.call_later( + MS, self.receive_frame, Frame(True, OP_CLOSE, close_frame_data) + ) + self.loop.call_later(2 * MS, self.receive_eof_if_client) + + # This task must be awaited or canceled by the caller. + return close_task + + def half_close_connection_remote(self, code=1000, reason="close"): + """ + Receive a closing handshake but do not complete it. + + The main difference with `close_connection` is that the connection is + left in the CLOSING state until the event loop runs again. + + """ + # On the server side, websockets completes the closing handshake and + # closes the TCP connection immediately. Yield to the event loop after + # sending the close frame to run the test while the connection is in + # the CLOSING state. + if not self.protocol.is_client: + self.make_drain_slow() + + close_frame_data = serialize_close(code, reason) + # Trigger the closing handshake from the remote endpoint. + self.receive_frame(Frame(True, OP_CLOSE, close_frame_data)) + self.run_loop_once() # read_frame executes + # Empty the outgoing data stream so we can make assertions later on. + self.assertOneFrameSent(True, OP_CLOSE, close_frame_data) + + assert self.protocol.state is State.CLOSING + + # Complete the closing sequence at 1ms intervals so the test can run + # at each point even it goes back to the event loop several times. + self.loop.call_later(2 * MS, self.receive_eof_if_client) + + def process_invalid_frames(self): + """ + Make the protocol fail quickly after simulating invalid data. + + To achieve this, this function triggers the protocol's eof_received, + which interrupts pending reads waiting for more data. + + """ + self.run_loop_once() + self.receive_eof() + self.loop.run_until_complete(self.protocol.close_connection_task) + + def sent_frames(self): + """ + Read all frames sent to the transport. + + """ + stream = asyncio.StreamReader(loop=self.loop) + + for (data,), kw in self.transport.write.call_args_list: + stream.feed_data(data) + self.transport.write.call_args_list = [] + stream.feed_eof() + + frames = [] + while not stream.at_eof(): + frames.append( + self.loop.run_until_complete( + Frame.read(stream.readexactly, mask=self.protocol.is_client) + ) + ) + return frames + + def last_sent_frame(self): + """ + Read the last frame sent to the transport. + + This method assumes that at most one frame was sent. It raises an + AssertionError otherwise. + + """ + frames = self.sent_frames() + if frames: + assert len(frames) == 1 + return frames[0] + + def assertFramesSent(self, *frames): + self.assertEqual(self.sent_frames(), [Frame(*args) for args in frames]) + + def assertOneFrameSent(self, *args): + self.assertEqual(self.last_sent_frame(), Frame(*args)) + + def assertNoFrameSent(self): + self.assertIsNone(self.last_sent_frame()) + + def assertConnectionClosed(self, code, message): + # The following line guarantees that connection_lost was called. + self.assertEqual(self.protocol.state, State.CLOSED) + # A close frame was received. + self.assertEqual(self.protocol.close_code, code) + self.assertEqual(self.protocol.close_reason, message) + + def assertConnectionFailed(self, code, message): + # The following line guarantees that connection_lost was called. + self.assertEqual(self.protocol.state, State.CLOSED) + # No close frame was received. + self.assertEqual(self.protocol.close_code, 1006) + self.assertEqual(self.protocol.close_reason, "") + # A close frame was sent -- unless the connection was already lost. + if code == 1006: + self.assertNoFrameSent() + else: + self.assertOneFrameSent(True, OP_CLOSE, serialize_close(code, message)) + + @contextlib.contextmanager + def assertCompletesWithin(self, min_time, max_time): + t0 = self.loop.time() + yield + t1 = self.loop.time() + dt = t1 - t0 + self.assertGreaterEqual(dt, min_time, f"Too fast: {dt} < {min_time}") + self.assertLess(dt, max_time, f"Too slow: {dt} >= {max_time}") + + # Test constructor. + + def test_timeout_backwards_compatibility(self): + with warnings.catch_warnings(record=True) as recorded_warnings: + protocol = WebSocketCommonProtocol(timeout=5) + + self.assertEqual(protocol.close_timeout, 5) + + self.assertEqual(len(recorded_warnings), 1) + warning = recorded_warnings[0].message + self.assertEqual(str(warning), "rename timeout to close_timeout") + self.assertEqual(type(warning), DeprecationWarning) + + # Test public attributes. + + def test_local_address(self): + get_extra_info = unittest.mock.Mock(return_value=("host", 4312)) + self.transport.get_extra_info = get_extra_info + + self.assertEqual(self.protocol.local_address, ("host", 4312)) + get_extra_info.assert_called_with("sockname") + + def test_local_address_before_connection(self): + # Emulate the situation before connection_open() runs. + _transport = self.protocol.transport + del self.protocol.transport + try: + self.assertEqual(self.protocol.local_address, None) + finally: + self.protocol.transport = _transport + + def test_remote_address(self): + get_extra_info = unittest.mock.Mock(return_value=("host", 4312)) + self.transport.get_extra_info = get_extra_info + + self.assertEqual(self.protocol.remote_address, ("host", 4312)) + get_extra_info.assert_called_with("peername") + + def test_remote_address_before_connection(self): + # Emulate the situation before connection_open() runs. + _transport = self.protocol.transport + del self.protocol.transport + try: + self.assertEqual(self.protocol.remote_address, None) + finally: + self.protocol.transport = _transport + + def test_open(self): + self.assertTrue(self.protocol.open) + self.close_connection() + self.assertFalse(self.protocol.open) + + def test_closed(self): + self.assertFalse(self.protocol.closed) + self.close_connection() + self.assertTrue(self.protocol.closed) + + def test_wait_closed(self): + wait_closed = self.loop.create_task(self.protocol.wait_closed()) + self.assertFalse(wait_closed.done()) + self.close_connection() + self.assertTrue(wait_closed.done()) + + # Test the recv coroutine. + + def test_recv_text(self): + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8"))) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, "café") + + def test_recv_binary(self): + self.receive_frame(Frame(True, OP_BINARY, b"tea")) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, b"tea") + + def test_recv_on_closing_connection_local(self): + close_task = self.half_close_connection_local() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.recv()) + + self.loop.run_until_complete(close_task) # cleanup + + def test_recv_on_closing_connection_remote(self): + self.half_close_connection_remote() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.recv()) + + def test_recv_on_closed_connection(self): + self.close_connection() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.recv()) + + def test_recv_protocol_error(self): + self.receive_frame(Frame(True, OP_CONT, "café".encode("utf-8"))) + self.process_invalid_frames() + self.assertConnectionFailed(1002, "") + + def test_recv_unicode_error(self): + self.receive_frame(Frame(True, OP_TEXT, "café".encode("latin-1"))) + self.process_invalid_frames() + self.assertConnectionFailed(1007, "") + + def test_recv_text_payload_too_big(self): + self.protocol.max_size = 1024 + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8") * 205)) + self.process_invalid_frames() + self.assertConnectionFailed(1009, "") + + def test_recv_binary_payload_too_big(self): + self.protocol.max_size = 1024 + self.receive_frame(Frame(True, OP_BINARY, b"tea" * 342)) + self.process_invalid_frames() + self.assertConnectionFailed(1009, "") + + def test_recv_text_no_max_size(self): + self.protocol.max_size = None # for test coverage + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8") * 205)) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, "café" * 205) + + def test_recv_binary_no_max_size(self): + self.protocol.max_size = None # for test coverage + self.receive_frame(Frame(True, OP_BINARY, b"tea" * 342)) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, b"tea" * 342) + + def test_recv_queue_empty(self): + recv = self.loop.create_task(self.protocol.recv()) + with self.assertRaises(asyncio.TimeoutError): + self.loop.run_until_complete( + asyncio.wait_for(asyncio.shield(recv), timeout=MS) + ) + + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8"))) + data = self.loop.run_until_complete(recv) + self.assertEqual(data, "café") + + def test_recv_queue_full(self): + self.protocol.max_queue = 2 + # Test internals because it's hard to verify buffers from the outside. + self.assertEqual(list(self.protocol.messages), []) + + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8"))) + self.run_loop_once() + self.assertEqual(list(self.protocol.messages), ["café"]) + + self.receive_frame(Frame(True, OP_BINARY, b"tea")) + self.run_loop_once() + self.assertEqual(list(self.protocol.messages), ["café", b"tea"]) + + self.receive_frame(Frame(True, OP_BINARY, b"milk")) + self.run_loop_once() + self.assertEqual(list(self.protocol.messages), ["café", b"tea"]) + + self.loop.run_until_complete(self.protocol.recv()) + self.run_loop_once() + self.assertEqual(list(self.protocol.messages), [b"tea", b"milk"]) + + self.loop.run_until_complete(self.protocol.recv()) + self.run_loop_once() + self.assertEqual(list(self.protocol.messages), [b"milk"]) + + self.loop.run_until_complete(self.protocol.recv()) + self.run_loop_once() + self.assertEqual(list(self.protocol.messages), []) + + def test_recv_queue_no_limit(self): + self.protocol.max_queue = None + + for _ in range(100): + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8"))) + self.run_loop_once() + + # Incoming message queue can contain at least 100 messages. + self.assertEqual(list(self.protocol.messages), ["café"] * 100) + + for _ in range(100): + self.loop.run_until_complete(self.protocol.recv()) + + self.assertEqual(list(self.protocol.messages), []) + + def test_recv_other_error(self): + async def read_message(): + raise Exception("BOOM") + + self.protocol.read_message = read_message + self.process_invalid_frames() + self.assertConnectionFailed(1011, "") + + def test_recv_canceled(self): + recv = self.loop.create_task(self.protocol.recv()) + self.loop.call_soon(recv.cancel) + + with self.assertRaises(asyncio.CancelledError): + self.loop.run_until_complete(recv) + + # The next frame doesn't disappear in a vacuum (it used to). + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8"))) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, "café") + + def test_recv_canceled_race_condition(self): + recv = self.loop.create_task( + asyncio.wait_for(self.protocol.recv(), timeout=0.000_001) + ) + self.loop.call_soon( + self.receive_frame, Frame(True, OP_TEXT, "café".encode("utf-8")) + ) + + with self.assertRaises(asyncio.TimeoutError): + self.loop.run_until_complete(recv) + + # The previous frame doesn't disappear in a vacuum (it used to). + self.receive_frame(Frame(True, OP_TEXT, "tea".encode("utf-8"))) + data = self.loop.run_until_complete(self.protocol.recv()) + # If we're getting "tea" there, it means "café" was swallowed (ha, ha). + self.assertEqual(data, "café") + + def test_recv_when_transfer_data_cancelled(self): + # Clog incoming queue. + self.protocol.max_queue = 1 + self.receive_frame(Frame(True, OP_TEXT, "café".encode("utf-8"))) + self.receive_frame(Frame(True, OP_BINARY, b"tea")) + self.run_loop_once() + + # Flow control kicks in (check with an implementation detail). + self.assertFalse(self.protocol._put_message_waiter.done()) + + # Schedule recv(). + recv = self.loop.create_task(self.protocol.recv()) + + # Cancel transfer_data_task (again, implementation detail). + self.protocol.fail_connection() + self.run_loop_once() + self.assertTrue(self.protocol.transfer_data_task.cancelled()) + + # recv() completes properly. + self.assertEqual(self.loop.run_until_complete(recv), "café") + + def test_recv_prevents_concurrent_calls(self): + recv = self.loop.create_task(self.protocol.recv()) + + with self.assertRaisesRegex( + RuntimeError, + "cannot call recv while another coroutine " + "is already waiting for the next message", + ): + self.loop.run_until_complete(self.protocol.recv()) + + recv.cancel() + + # Test the send coroutine. + + def test_send_text(self): + self.loop.run_until_complete(self.protocol.send("café")) + self.assertOneFrameSent(True, OP_TEXT, "café".encode("utf-8")) + + def test_send_binary(self): + self.loop.run_until_complete(self.protocol.send(b"tea")) + self.assertOneFrameSent(True, OP_BINARY, b"tea") + + def test_send_binary_from_bytearray(self): + self.loop.run_until_complete(self.protocol.send(bytearray(b"tea"))) + self.assertOneFrameSent(True, OP_BINARY, b"tea") + + def test_send_binary_from_memoryview(self): + self.loop.run_until_complete(self.protocol.send(memoryview(b"tea"))) + self.assertOneFrameSent(True, OP_BINARY, b"tea") + + def test_send_binary_from_non_contiguous_memoryview(self): + self.loop.run_until_complete(self.protocol.send(memoryview(b"tteeaa")[::2])) + self.assertOneFrameSent(True, OP_BINARY, b"tea") + + def test_send_type_error(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete(self.protocol.send(42)) + self.assertNoFrameSent() + + def test_send_iterable_text(self): + self.loop.run_until_complete(self.protocol.send(["ca", "fé"])) + self.assertFramesSent( + (False, OP_TEXT, "ca".encode("utf-8")), + (False, OP_CONT, "fé".encode("utf-8")), + (True, OP_CONT, "".encode("utf-8")), + ) + + def test_send_iterable_binary(self): + self.loop.run_until_complete(self.protocol.send([b"te", b"a"])) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_iterable_binary_from_bytearray(self): + self.loop.run_until_complete( + self.protocol.send([bytearray(b"te"), bytearray(b"a")]) + ) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_iterable_binary_from_memoryview(self): + self.loop.run_until_complete( + self.protocol.send([memoryview(b"te"), memoryview(b"a")]) + ) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_iterable_binary_from_non_contiguous_memoryview(self): + self.loop.run_until_complete( + self.protocol.send([memoryview(b"ttee")[::2], memoryview(b"aa")[::2]]) + ) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_empty_iterable(self): + self.loop.run_until_complete(self.protocol.send([])) + self.assertNoFrameSent() + + def test_send_iterable_type_error(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete(self.protocol.send([42])) + self.assertNoFrameSent() + + def test_send_iterable_mixed_type_error(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete(self.protocol.send(["café", b"tea"])) + self.assertFramesSent( + (False, OP_TEXT, "café".encode("utf-8")), + (True, OP_CLOSE, serialize_close(1011, "")), + ) + + def test_send_iterable_prevents_concurrent_send(self): + self.make_drain_slow(2 * MS) + + async def send_iterable(): + await self.protocol.send(["ca", "fé"]) + + async def send_concurrent(): + await asyncio.sleep(MS) + await self.protocol.send(b"tea") + + self.loop.run_until_complete(asyncio.gather(send_iterable(), send_concurrent())) + self.assertFramesSent( + (False, OP_TEXT, "ca".encode("utf-8")), + (False, OP_CONT, "fé".encode("utf-8")), + (True, OP_CONT, "".encode("utf-8")), + (True, OP_BINARY, b"tea"), + ) + + def test_send_async_iterable_text(self): + self.loop.run_until_complete(self.protocol.send(async_iterable(["ca", "fé"]))) + self.assertFramesSent( + (False, OP_TEXT, "ca".encode("utf-8")), + (False, OP_CONT, "fé".encode("utf-8")), + (True, OP_CONT, "".encode("utf-8")), + ) + + def test_send_async_iterable_binary(self): + self.loop.run_until_complete(self.protocol.send(async_iterable([b"te", b"a"]))) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_async_iterable_binary_from_bytearray(self): + self.loop.run_until_complete( + self.protocol.send(async_iterable([bytearray(b"te"), bytearray(b"a")])) + ) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_async_iterable_binary_from_memoryview(self): + self.loop.run_until_complete( + self.protocol.send(async_iterable([memoryview(b"te"), memoryview(b"a")])) + ) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_async_iterable_binary_from_non_contiguous_memoryview(self): + self.loop.run_until_complete( + self.protocol.send( + async_iterable([memoryview(b"ttee")[::2], memoryview(b"aa")[::2]]) + ) + ) + self.assertFramesSent( + (False, OP_BINARY, b"te"), (False, OP_CONT, b"a"), (True, OP_CONT, b"") + ) + + def test_send_empty_async_iterable(self): + self.loop.run_until_complete(self.protocol.send(async_iterable([]))) + self.assertNoFrameSent() + + def test_send_async_iterable_type_error(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete(self.protocol.send(async_iterable([42]))) + self.assertNoFrameSent() + + def test_send_async_iterable_mixed_type_error(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete( + self.protocol.send(async_iterable(["café", b"tea"])) + ) + self.assertFramesSent( + (False, OP_TEXT, "café".encode("utf-8")), + (True, OP_CLOSE, serialize_close(1011, "")), + ) + + def test_send_async_iterable_prevents_concurrent_send(self): + self.make_drain_slow(2 * MS) + + async def send_async_iterable(): + await self.protocol.send(async_iterable(["ca", "fé"])) + + async def send_concurrent(): + await asyncio.sleep(MS) + await self.protocol.send(b"tea") + + self.loop.run_until_complete( + asyncio.gather(send_async_iterable(), send_concurrent()) + ) + self.assertFramesSent( + (False, OP_TEXT, "ca".encode("utf-8")), + (False, OP_CONT, "fé".encode("utf-8")), + (True, OP_CONT, "".encode("utf-8")), + (True, OP_BINARY, b"tea"), + ) + + def test_send_on_closing_connection_local(self): + close_task = self.half_close_connection_local() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.send("foobar")) + + self.assertNoFrameSent() + + self.loop.run_until_complete(close_task) # cleanup + + def test_send_on_closing_connection_remote(self): + self.half_close_connection_remote() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.send("foobar")) + + self.assertNoFrameSent() + + def test_send_on_closed_connection(self): + self.close_connection() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.send("foobar")) + + self.assertNoFrameSent() + + # Test the ping coroutine. + + def test_ping_default(self): + self.loop.run_until_complete(self.protocol.ping()) + # With our testing tools, it's more convenient to extract the expected + # ping data from the library's internals than from the frame sent. + ping_data = next(iter(self.protocol.pings)) + self.assertIsInstance(ping_data, bytes) + self.assertEqual(len(ping_data), 4) + self.assertOneFrameSent(True, OP_PING, ping_data) + + def test_ping_text(self): + self.loop.run_until_complete(self.protocol.ping("café")) + self.assertOneFrameSent(True, OP_PING, "café".encode("utf-8")) + + def test_ping_binary(self): + self.loop.run_until_complete(self.protocol.ping(b"tea")) + self.assertOneFrameSent(True, OP_PING, b"tea") + + def test_ping_binary_from_bytearray(self): + self.loop.run_until_complete(self.protocol.ping(bytearray(b"tea"))) + self.assertOneFrameSent(True, OP_PING, b"tea") + + def test_ping_binary_from_memoryview(self): + self.loop.run_until_complete(self.protocol.ping(memoryview(b"tea"))) + self.assertOneFrameSent(True, OP_PING, b"tea") + + def test_ping_binary_from_non_contiguous_memoryview(self): + self.loop.run_until_complete(self.protocol.ping(memoryview(b"tteeaa")[::2])) + self.assertOneFrameSent(True, OP_PING, b"tea") + + def test_ping_type_error(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete(self.protocol.ping(42)) + self.assertNoFrameSent() + + def test_ping_on_closing_connection_local(self): + close_task = self.half_close_connection_local() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.ping()) + + self.assertNoFrameSent() + + self.loop.run_until_complete(close_task) # cleanup + + def test_ping_on_closing_connection_remote(self): + self.half_close_connection_remote() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.ping()) + + self.assertNoFrameSent() + + def test_ping_on_closed_connection(self): + self.close_connection() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.ping()) + + self.assertNoFrameSent() + + # Test the pong coroutine. + + def test_pong_default(self): + self.loop.run_until_complete(self.protocol.pong()) + self.assertOneFrameSent(True, OP_PONG, b"") + + def test_pong_text(self): + self.loop.run_until_complete(self.protocol.pong("café")) + self.assertOneFrameSent(True, OP_PONG, "café".encode("utf-8")) + + def test_pong_binary(self): + self.loop.run_until_complete(self.protocol.pong(b"tea")) + self.assertOneFrameSent(True, OP_PONG, b"tea") + + def test_pong_binary_from_bytearray(self): + self.loop.run_until_complete(self.protocol.pong(bytearray(b"tea"))) + self.assertOneFrameSent(True, OP_PONG, b"tea") + + def test_pong_binary_from_memoryview(self): + self.loop.run_until_complete(self.protocol.pong(memoryview(b"tea"))) + self.assertOneFrameSent(True, OP_PONG, b"tea") + + def test_pong_binary_from_non_contiguous_memoryview(self): + self.loop.run_until_complete(self.protocol.pong(memoryview(b"tteeaa")[::2])) + self.assertOneFrameSent(True, OP_PONG, b"tea") + + def test_pong_type_error(self): + with self.assertRaises(TypeError): + self.loop.run_until_complete(self.protocol.pong(42)) + self.assertNoFrameSent() + + def test_pong_on_closing_connection_local(self): + close_task = self.half_close_connection_local() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.pong()) + + self.assertNoFrameSent() + + self.loop.run_until_complete(close_task) # cleanup + + def test_pong_on_closing_connection_remote(self): + self.half_close_connection_remote() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.pong()) + + self.assertNoFrameSent() + + def test_pong_on_closed_connection(self): + self.close_connection() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.pong()) + + self.assertNoFrameSent() + + # Test the protocol's logic for acknowledging pings with pongs. + + def test_answer_ping(self): + self.receive_frame(Frame(True, OP_PING, b"test")) + self.run_loop_once() + self.assertOneFrameSent(True, OP_PONG, b"test") + + def test_ignore_pong(self): + self.receive_frame(Frame(True, OP_PONG, b"test")) + self.run_loop_once() + self.assertNoFrameSent() + + def test_acknowledge_ping(self): + ping = self.loop.run_until_complete(self.protocol.ping()) + self.assertFalse(ping.done()) + ping_frame = self.last_sent_frame() + pong_frame = Frame(True, OP_PONG, ping_frame.data) + self.receive_frame(pong_frame) + self.run_loop_once() + self.run_loop_once() + self.assertTrue(ping.done()) + + def test_abort_ping(self): + ping = self.loop.run_until_complete(self.protocol.ping()) + # Remove the frame from the buffer, else close_connection() complains. + self.last_sent_frame() + self.assertFalse(ping.done()) + self.close_connection() + self.assertTrue(ping.done()) + self.assertIsInstance(ping.exception(), ConnectionClosed) + + def test_abort_ping_does_not_log_exception_if_not_retreived(self): + self.loop.run_until_complete(self.protocol.ping()) + # Get the internal Future, which isn't directly returned by ping(). + (ping,) = self.protocol.pings.values() + # Remove the frame from the buffer, else close_connection() complains. + self.last_sent_frame() + self.close_connection() + # Check a private attribute, for lack of a better solution. + self.assertFalse(ping._log_traceback) + + def test_acknowledge_previous_pings(self): + pings = [ + (self.loop.run_until_complete(self.protocol.ping()), self.last_sent_frame()) + for i in range(3) + ] + # Unsolicited pong doesn't acknowledge pings + self.receive_frame(Frame(True, OP_PONG, b"")) + self.run_loop_once() + self.run_loop_once() + self.assertFalse(pings[0][0].done()) + self.assertFalse(pings[1][0].done()) + self.assertFalse(pings[2][0].done()) + # Pong acknowledges all previous pings + self.receive_frame(Frame(True, OP_PONG, pings[1][1].data)) + self.run_loop_once() + self.run_loop_once() + self.assertTrue(pings[0][0].done()) + self.assertTrue(pings[1][0].done()) + self.assertFalse(pings[2][0].done()) + + def test_acknowledge_aborted_ping(self): + ping = self.loop.run_until_complete(self.protocol.ping()) + ping_frame = self.last_sent_frame() + # Clog incoming queue. This lets connection_lost() abort pending pings + # with a ConnectionClosed exception before transfer_data_task + # terminates and close_connection cancels keepalive_ping_task. + self.protocol.max_queue = 1 + self.receive_frame(Frame(True, OP_TEXT, b"1")) + self.receive_frame(Frame(True, OP_TEXT, b"2")) + # Add pong frame to the queue. + pong_frame = Frame(True, OP_PONG, ping_frame.data) + self.receive_frame(pong_frame) + # Connection drops. + self.receive_eof() + self.loop.run_until_complete(self.protocol.wait_closed()) + # Ping receives a ConnectionClosed exception. + with self.assertRaises(ConnectionClosed): + ping.result() + + # transfer_data doesn't crash, which would be logged. + with self.assertNoLogs(): + # Unclog incoming queue. + self.loop.run_until_complete(self.protocol.recv()) + self.loop.run_until_complete(self.protocol.recv()) + + def test_canceled_ping(self): + ping = self.loop.run_until_complete(self.protocol.ping()) + ping_frame = self.last_sent_frame() + ping.cancel() + pong_frame = Frame(True, OP_PONG, ping_frame.data) + self.receive_frame(pong_frame) + self.run_loop_once() + self.run_loop_once() + self.assertTrue(ping.cancelled()) + + def test_duplicate_ping(self): + self.loop.run_until_complete(self.protocol.ping(b"foobar")) + self.assertOneFrameSent(True, OP_PING, b"foobar") + with self.assertRaises(ValueError): + self.loop.run_until_complete(self.protocol.ping(b"foobar")) + self.assertNoFrameSent() + + # Test the protocol's logic for rebuilding fragmented messages. + + def test_fragmented_text(self): + self.receive_frame(Frame(False, OP_TEXT, "ca".encode("utf-8"))) + self.receive_frame(Frame(True, OP_CONT, "fé".encode("utf-8"))) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, "café") + + def test_fragmented_binary(self): + self.receive_frame(Frame(False, OP_BINARY, b"t")) + self.receive_frame(Frame(False, OP_CONT, b"e")) + self.receive_frame(Frame(True, OP_CONT, b"a")) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, b"tea") + + def test_fragmented_text_payload_too_big(self): + self.protocol.max_size = 1024 + self.receive_frame(Frame(False, OP_TEXT, "café".encode("utf-8") * 100)) + self.receive_frame(Frame(True, OP_CONT, "café".encode("utf-8") * 105)) + self.process_invalid_frames() + self.assertConnectionFailed(1009, "") + + def test_fragmented_binary_payload_too_big(self): + self.protocol.max_size = 1024 + self.receive_frame(Frame(False, OP_BINARY, b"tea" * 171)) + self.receive_frame(Frame(True, OP_CONT, b"tea" * 171)) + self.process_invalid_frames() + self.assertConnectionFailed(1009, "") + + def test_fragmented_text_no_max_size(self): + self.protocol.max_size = None # for test coverage + self.receive_frame(Frame(False, OP_TEXT, "café".encode("utf-8") * 100)) + self.receive_frame(Frame(True, OP_CONT, "café".encode("utf-8") * 105)) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, "café" * 205) + + def test_fragmented_binary_no_max_size(self): + self.protocol.max_size = None # for test coverage + self.receive_frame(Frame(False, OP_BINARY, b"tea" * 171)) + self.receive_frame(Frame(True, OP_CONT, b"tea" * 171)) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, b"tea" * 342) + + def test_control_frame_within_fragmented_text(self): + self.receive_frame(Frame(False, OP_TEXT, "ca".encode("utf-8"))) + self.receive_frame(Frame(True, OP_PING, b"")) + self.receive_frame(Frame(True, OP_CONT, "fé".encode("utf-8"))) + data = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(data, "café") + self.assertOneFrameSent(True, OP_PONG, b"") + + def test_unterminated_fragmented_text(self): + self.receive_frame(Frame(False, OP_TEXT, "ca".encode("utf-8"))) + # Missing the second part of the fragmented frame. + self.receive_frame(Frame(True, OP_BINARY, b"tea")) + self.process_invalid_frames() + self.assertConnectionFailed(1002, "") + + def test_close_handshake_in_fragmented_text(self): + self.receive_frame(Frame(False, OP_TEXT, "ca".encode("utf-8"))) + self.receive_frame(Frame(True, OP_CLOSE, b"")) + self.process_invalid_frames() + # The RFC may have overlooked this case: it says that control frames + # can be interjected in the middle of a fragmented message and that a + # close frame must be echoed. Even though there's an unterminated + # message, technically, the closing handshake was successful. + self.assertConnectionClosed(1005, "") + + def test_connection_close_in_fragmented_text(self): + self.receive_frame(Frame(False, OP_TEXT, "ca".encode("utf-8"))) + self.process_invalid_frames() + self.assertConnectionFailed(1006, "") + + # Test miscellaneous code paths to ensure full coverage. + + def test_connection_lost(self): + # Test calling connection_lost without going through close_connection. + self.protocol.connection_lost(None) + + self.assertConnectionFailed(1006, "") + + def test_ensure_open_before_opening_handshake(self): + # Simulate a bug by forcibly reverting the protocol state. + self.protocol.state = State.CONNECTING + + with self.assertRaises(InvalidState): + self.loop.run_until_complete(self.protocol.ensure_open()) + + def test_ensure_open_during_unclean_close(self): + # Process connection_made in order to start transfer_data_task. + self.run_loop_once() + + # Ensure the test terminates quickly. + self.loop.call_later(MS, self.receive_eof_if_client) + + # Simulate the case when close() times out sending a close frame. + self.protocol.fail_connection() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.ensure_open()) + + def test_legacy_recv(self): + # By default legacy_recv in disabled. + self.assertEqual(self.protocol.legacy_recv, False) + + self.close_connection() + + # Enable legacy_recv. + self.protocol.legacy_recv = True + + # Now recv() returns None instead of raising ConnectionClosed. + self.assertIsNone(self.loop.run_until_complete(self.protocol.recv())) + + def test_connection_closed_attributes(self): + self.close_connection() + + with self.assertRaises(ConnectionClosed) as context: + self.loop.run_until_complete(self.protocol.recv()) + + connection_closed_exc = context.exception + self.assertEqual(connection_closed_exc.code, 1000) + self.assertEqual(connection_closed_exc.reason, "close") + + # Test the protocol logic for sending keepalive pings. + + def restart_protocol_with_keepalive_ping( + self, ping_interval=3 * MS, ping_timeout=3 * MS + ): + initial_protocol = self.protocol + # copied from tearDown + self.transport.close() + self.loop.run_until_complete(self.protocol.close()) + # copied from setUp, but enables keepalive pings + self.protocol = WebSocketCommonProtocol( + ping_interval=ping_interval, ping_timeout=ping_timeout + ) + self.transport = TransportMock() + self.transport.setup_mock(self.loop, self.protocol) + self.protocol.is_client = initial_protocol.is_client + self.protocol.side = initial_protocol.side + + def test_keepalive_ping(self): + self.restart_protocol_with_keepalive_ping() + + # Ping is sent at 3ms and acknowledged at 4ms. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + (ping_1,) = tuple(self.protocol.pings) + self.assertOneFrameSent(True, OP_PING, ping_1) + self.receive_frame(Frame(True, OP_PONG, ping_1)) + + # Next ping is sent at 7ms. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + (ping_2,) = tuple(self.protocol.pings) + self.assertOneFrameSent(True, OP_PING, ping_2) + + # The keepalive ping task goes on. + self.assertFalse(self.protocol.keepalive_ping_task.done()) + + def test_keepalive_ping_not_acknowledged_closes_connection(self): + self.restart_protocol_with_keepalive_ping() + + # Ping is sent at 3ms and not acknowleged. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + (ping_1,) = tuple(self.protocol.pings) + self.assertOneFrameSent(True, OP_PING, ping_1) + + # Connection is closed at 6ms. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + self.assertOneFrameSent(True, OP_CLOSE, serialize_close(1011, "")) + + # The keepalive ping task is complete. + self.assertEqual(self.protocol.keepalive_ping_task.result(), None) + + def test_keepalive_ping_stops_when_connection_closing(self): + self.restart_protocol_with_keepalive_ping() + close_task = self.half_close_connection_local() + + # No ping sent at 3ms because the closing handshake is in progress. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + self.assertNoFrameSent() + + # The keepalive ping task terminated. + self.assertTrue(self.protocol.keepalive_ping_task.cancelled()) + + self.loop.run_until_complete(close_task) # cleanup + + def test_keepalive_ping_stops_when_connection_closed(self): + self.restart_protocol_with_keepalive_ping() + self.close_connection() + + # The keepalive ping task terminated. + self.assertTrue(self.protocol.keepalive_ping_task.cancelled()) + + def test_keepalive_ping_does_not_crash_when_connection_lost(self): + self.restart_protocol_with_keepalive_ping() + # Clog incoming queue. This lets connection_lost() abort pending pings + # with a ConnectionClosed exception before transfer_data_task + # terminates and close_connection cancels keepalive_ping_task. + self.protocol.max_queue = 1 + self.receive_frame(Frame(True, OP_TEXT, b"1")) + self.receive_frame(Frame(True, OP_TEXT, b"2")) + # Ping is sent at 3ms. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + (ping_waiter,) = tuple(self.protocol.pings.values()) + # Connection drops. + self.receive_eof() + self.loop.run_until_complete(self.protocol.wait_closed()) + + # The ping waiter receives a ConnectionClosed exception. + with self.assertRaises(ConnectionClosed): + ping_waiter.result() + # The keepalive ping task terminated properly. + self.assertIsNone(self.protocol.keepalive_ping_task.result()) + + # Unclog incoming queue to terminate the test quickly. + self.loop.run_until_complete(self.protocol.recv()) + self.loop.run_until_complete(self.protocol.recv()) + + def test_keepalive_ping_with_no_ping_interval(self): + self.restart_protocol_with_keepalive_ping(ping_interval=None) + + # No ping is sent at 3ms. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + self.assertNoFrameSent() + + def test_keepalive_ping_with_no_ping_timeout(self): + self.restart_protocol_with_keepalive_ping(ping_timeout=None) + + # Ping is sent at 3ms and not acknowleged. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + (ping_1,) = tuple(self.protocol.pings) + self.assertOneFrameSent(True, OP_PING, ping_1) + + # Next ping is sent at 7ms anyway. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + ping_1_again, ping_2 = tuple(self.protocol.pings) + self.assertEqual(ping_1, ping_1_again) + self.assertOneFrameSent(True, OP_PING, ping_2) + + # The keepalive ping task goes on. + self.assertFalse(self.protocol.keepalive_ping_task.done()) + + def test_keepalive_ping_unexpected_error(self): + self.restart_protocol_with_keepalive_ping() + + async def ping(): + raise Exception("BOOM") + + self.protocol.ping = ping + + # The keepalive ping task fails when sending a ping at 3ms. + self.loop.run_until_complete(asyncio.sleep(4 * MS)) + + # The keepalive ping task is complete. + # It logs and swallows the exception. + self.assertEqual(self.protocol.keepalive_ping_task.result(), None) + + # Test the protocol logic for closing the connection. + + def test_local_close(self): + # Emulate how the remote endpoint answers the closing handshake. + self.loop.call_later(MS, self.receive_frame, self.close_frame) + self.loop.call_later(MS, self.receive_eof_if_client) + + # Run the closing handshake. + self.loop.run_until_complete(self.protocol.close(reason="close")) + + self.assertConnectionClosed(1000, "close") + self.assertOneFrameSent(*self.close_frame) + + # Closing the connection again is a no-op. + self.loop.run_until_complete(self.protocol.close(reason="oh noes!")) + + self.assertConnectionClosed(1000, "close") + self.assertNoFrameSent() + + def test_remote_close(self): + # Emulate how the remote endpoint initiates the closing handshake. + self.loop.call_later(MS, self.receive_frame, self.close_frame) + self.loop.call_later(MS, self.receive_eof_if_client) + + # Wait for some data in order to process the handshake. + # After recv() raises ConnectionClosed, the connection is closed. + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(self.protocol.recv()) + + self.assertConnectionClosed(1000, "close") + self.assertOneFrameSent(*self.close_frame) + + # Closing the connection again is a no-op. + self.loop.run_until_complete(self.protocol.close(reason="oh noes!")) + + self.assertConnectionClosed(1000, "close") + self.assertNoFrameSent() + + def test_remote_close_and_connection_lost(self): + self.make_drain_slow() + # Drop the connection right after receiving a close frame, + # which prevents echoing the close frame properly. + self.receive_frame(self.close_frame) + self.receive_eof() + + with self.assertNoLogs(): + self.loop.run_until_complete(self.protocol.close(reason="oh noes!")) + + self.assertConnectionClosed(1000, "close") + self.assertOneFrameSent(*self.close_frame) + + def test_simultaneous_close(self): + # Receive the incoming close frame right after self.protocol.close() + # starts executing. This reproduces the error described in: + # https://github.com/aaugustin/websockets/issues/339 + self.loop.call_soon(self.receive_frame, self.remote_close) + self.loop.call_soon(self.receive_eof_if_client) + + self.loop.run_until_complete(self.protocol.close(reason="local")) + + self.assertConnectionClosed(1000, "remote") + # The current implementation sends a close frame in response to the + # close frame received from the remote end. It skips the close frame + # that should be sent as a result of calling close(). + self.assertOneFrameSent(*self.remote_close) + + def test_close_preserves_incoming_frames(self): + self.receive_frame(Frame(True, OP_TEXT, b"hello")) + + self.loop.call_later(MS, self.receive_frame, self.close_frame) + self.loop.call_later(MS, self.receive_eof_if_client) + self.loop.run_until_complete(self.protocol.close(reason="close")) + + self.assertConnectionClosed(1000, "close") + self.assertOneFrameSent(*self.close_frame) + + next_message = self.loop.run_until_complete(self.protocol.recv()) + self.assertEqual(next_message, "hello") + + def test_close_protocol_error(self): + invalid_close_frame = Frame(True, OP_CLOSE, b"\x00") + self.receive_frame(invalid_close_frame) + self.receive_eof_if_client() + self.run_loop_once() + self.loop.run_until_complete(self.protocol.close(reason="close")) + + self.assertConnectionFailed(1002, "") + + def test_close_connection_lost(self): + self.receive_eof() + self.run_loop_once() + self.loop.run_until_complete(self.protocol.close(reason="close")) + + self.assertConnectionFailed(1006, "") + + def test_local_close_during_recv(self): + recv = self.loop.create_task(self.protocol.recv()) + + self.loop.call_later(MS, self.receive_frame, self.close_frame) + self.loop.call_later(MS, self.receive_eof_if_client) + + self.loop.run_until_complete(self.protocol.close(reason="close")) + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(recv) + + self.assertConnectionClosed(1000, "close") + + # There is no test_remote_close_during_recv because it would be identical + # to test_remote_close. + + def test_remote_close_during_send(self): + self.make_drain_slow() + send = self.loop.create_task(self.protocol.send("hello")) + + self.receive_frame(self.close_frame) + self.receive_eof() + + with self.assertRaises(ConnectionClosed): + self.loop.run_until_complete(send) + + self.assertConnectionClosed(1000, "close") + + # There is no test_local_close_during_send because this cannot really + # happen, considering that writes are serialized. + + +class ServerTests(CommonTests, AsyncioTestCase): + def setUp(self): + super().setUp() + self.protocol.is_client = False + self.protocol.side = "server" + + def test_local_close_send_close_frame_timeout(self): + self.protocol.close_timeout = 10 * MS + self.make_drain_slow(50 * MS) + # If we can't send a close frame, time out in 10ms. + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(9 * MS, 19 * MS): + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1006, "") + + def test_local_close_receive_close_frame_timeout(self): + self.protocol.close_timeout = 10 * MS + # If the client doesn't send a close frame, time out in 10ms. + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(9 * MS, 19 * MS): + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1006, "") + + def test_local_close_connection_lost_timeout_after_write_eof(self): + self.protocol.close_timeout = 10 * MS + # If the client doesn't close its side of the TCP connection after we + # half-close our side with write_eof(), time out in 10ms. + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(9 * MS, 19 * MS): + # HACK: disable write_eof => other end drops connection emulation. + self.transport._eof = True + self.receive_frame(self.close_frame) + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1000, "close") + + def test_local_close_connection_lost_timeout_after_close(self): + self.protocol.close_timeout = 10 * MS + # If the client doesn't close its side of the TCP connection after we + # half-close our side with write_eof() and close it with close(), time + # out in 20ms. + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(19 * MS, 29 * MS): + # HACK: disable write_eof => other end drops connection emulation. + self.transport._eof = True + # HACK: disable close => other end drops connection emulation. + self.transport._closing = True + self.receive_frame(self.close_frame) + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1000, "close") + + +class ClientTests(CommonTests, AsyncioTestCase): + def setUp(self): + super().setUp() + self.protocol.is_client = True + self.protocol.side = "client" + + def test_local_close_send_close_frame_timeout(self): + self.protocol.close_timeout = 10 * MS + self.make_drain_slow(50 * MS) + # If we can't send a close frame, time out in 20ms. + # - 10ms waiting for sending a close frame + # - 10ms waiting for receiving a half-close + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(19 * MS, 29 * MS): + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1006, "") + + def test_local_close_receive_close_frame_timeout(self): + self.protocol.close_timeout = 10 * MS + # If the server doesn't send a close frame, time out in 20ms: + # - 10ms waiting for receiving a close frame + # - 10ms waiting for receiving a half-close + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(19 * MS, 29 * MS): + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1006, "") + + def test_local_close_connection_lost_timeout_after_write_eof(self): + self.protocol.close_timeout = 10 * MS + # If the server doesn't half-close its side of the TCP connection + # after we send a close frame, time out in 20ms: + # - 10ms waiting for receiving a half-close + # - 10ms waiting for receiving a close after write_eof + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(19 * MS, 29 * MS): + # HACK: disable write_eof => other end drops connection emulation. + self.transport._eof = True + self.receive_frame(self.close_frame) + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1000, "close") + + def test_local_close_connection_lost_timeout_after_close(self): + self.protocol.close_timeout = 10 * MS + # If the client doesn't close its side of the TCP connection after we + # half-close our side with write_eof() and close it with close(), time + # out in 20ms. + # - 10ms waiting for receiving a half-close + # - 10ms waiting for receiving a close after write_eof + # - 10ms waiting for receiving a close after close + # Check the timing within -1/+9ms for robustness. + with self.assertCompletesWithin(29 * MS, 39 * MS): + # HACK: disable write_eof => other end drops connection emulation. + self.transport._eof = True + # HACK: disable close => other end drops connection emulation. + self.transport._closing = True + self.receive_frame(self.close_frame) + self.loop.run_until_complete(self.protocol.close(reason="close")) + self.assertConnectionClosed(1000, "close") diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_uri.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_uri.py new file mode 100644 index 00000000000..e41860b8e4c --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_uri.py @@ -0,0 +1,33 @@ +import unittest + +from websockets.exceptions import InvalidURI +from websockets.uri import * + + +VALID_URIS = [ + ("ws://localhost/", (False, "localhost", 80, "/", None)), + ("wss://localhost/", (True, "localhost", 443, "/", None)), + ("ws://localhost/path?query", (False, "localhost", 80, "/path?query", None)), + ("WS://LOCALHOST/PATH?QUERY", (False, "localhost", 80, "/PATH?QUERY", None)), + ("ws://user:pass@localhost/", (False, "localhost", 80, "/", ("user", "pass"))), +] + +INVALID_URIS = [ + "http://localhost/", + "https://localhost/", + "ws://localhost/path#fragment", + "ws://user@localhost/", +] + + +class URITests(unittest.TestCase): + def test_success(self): + for uri, parsed in VALID_URIS: + with self.subTest(uri=uri): + self.assertEqual(parse_uri(uri), parsed) + + def test_error(self): + for uri in INVALID_URIS: + with self.subTest(uri=uri): + with self.assertRaises(InvalidURI): + parse_uri(uri) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_utils.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_utils.py new file mode 100644 index 00000000000..e5570f098ba --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/test_utils.py @@ -0,0 +1,92 @@ +import itertools +import unittest + +from websockets.utils import apply_mask as py_apply_mask + + +class UtilsTests(unittest.TestCase): + @staticmethod + def apply_mask(*args, **kwargs): + return py_apply_mask(*args, **kwargs) + + apply_mask_type_combos = list(itertools.product([bytes, bytearray], repeat=2)) + + apply_mask_test_values = [ + (b"", b"1234", b""), + (b"aBcDe", b"\x00\x00\x00\x00", b"aBcDe"), + (b"abcdABCD", b"1234", b"PPPPpppp"), + (b"abcdABCD" * 10, b"1234", b"PPPPpppp" * 10), + ] + + def test_apply_mask(self): + for data_type, mask_type in self.apply_mask_type_combos: + for data_in, mask, data_out in self.apply_mask_test_values: + data_in, mask = data_type(data_in), mask_type(mask) + + with self.subTest(data_in=data_in, mask=mask): + result = self.apply_mask(data_in, mask) + self.assertEqual(result, data_out) + + def test_apply_mask_memoryview(self): + for data_type, mask_type in self.apply_mask_type_combos: + for data_in, mask, data_out in self.apply_mask_test_values: + data_in, mask = data_type(data_in), mask_type(mask) + data_in, mask = memoryview(data_in), memoryview(mask) + + with self.subTest(data_in=data_in, mask=mask): + result = self.apply_mask(data_in, mask) + self.assertEqual(result, data_out) + + def test_apply_mask_non_contiguous_memoryview(self): + for data_type, mask_type in self.apply_mask_type_combos: + for data_in, mask, data_out in self.apply_mask_test_values: + data_in, mask = data_type(data_in), mask_type(mask) + data_in, mask = memoryview(data_in), memoryview(mask) + data_in, mask = data_in[::-1], mask[::-1] + data_out = data_out[::-1] + + with self.subTest(data_in=data_in, mask=mask): + result = self.apply_mask(data_in, mask) + self.assertEqual(result, data_out) + + def test_apply_mask_check_input_types(self): + for data_in, mask in [(None, None), (b"abcd", None), (None, b"abcd")]: + with self.subTest(data_in=data_in, mask=mask): + with self.assertRaises(TypeError): + self.apply_mask(data_in, mask) + + def test_apply_mask_check_mask_length(self): + for data_in, mask in [ + (b"", b""), + (b"abcd", b"123"), + (b"", b"aBcDe"), + (b"12345678", b"12345678"), + ]: + with self.subTest(data_in=data_in, mask=mask): + with self.assertRaises(ValueError): + self.apply_mask(data_in, mask) + + +try: + from websockets.speedups import apply_mask as c_apply_mask +except ImportError: # pragma: no cover + pass +else: + + class SpeedupsTests(UtilsTests): + @staticmethod + def apply_mask(*args, **kwargs): + return c_apply_mask(*args, **kwargs) + + def test_apply_mask_non_contiguous_memoryview(self): + for data_type, mask_type in self.apply_mask_type_combos: + for data_in, mask, data_out in self.apply_mask_test_values: + data_in, mask = data_type(data_in), mask_type(mask) + data_in, mask = memoryview(data_in), memoryview(mask) + data_in, mask = data_in[::-1], mask[::-1] + data_out = data_out[::-1] + + with self.subTest(data_in=data_in, mask=mask): + # The C extension only supports contiguous memoryviews. + with self.assertRaises(TypeError): + self.apply_mask(data_in, mask) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/utils.py b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/utils.py new file mode 100644 index 00000000000..983a91edf06 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tests/utils.py @@ -0,0 +1,93 @@ +import asyncio +import contextlib +import functools +import logging +import os +import time +import unittest + + +class AsyncioTestCase(unittest.TestCase): + """ + Base class for tests that sets up an isolated event loop for each test. + + """ + + def __init_subclass__(cls, **kwargs): + """ + Convert test coroutines to test functions. + + This supports asychronous tests transparently. + + """ + super().__init_subclass__(**kwargs) + for name in unittest.defaultTestLoader.getTestCaseNames(cls): + test = getattr(cls, name) + if asyncio.iscoroutinefunction(test): + setattr(cls, name, cls.convert_async_to_sync(test)) + + @staticmethod + def convert_async_to_sync(test): + """ + Convert a test coroutine to a test function. + + """ + + @functools.wraps(test) + def test_func(self, *args, **kwargs): + return self.loop.run_until_complete(test(self, *args, **kwargs)) + + return test_func + + def setUp(self): + super().setUp() + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + def tearDown(self): + self.loop.close() + super().tearDown() + + def run_loop_once(self): + # Process callbacks scheduled with call_soon by appending a callback + # to stop the event loop then running it until it hits that callback. + self.loop.call_soon(self.loop.stop) + self.loop.run_forever() + + @contextlib.contextmanager + def assertNoLogs(self, logger="websockets", level=logging.ERROR): + """ + No message is logged on the given logger with at least the given level. + + """ + with self.assertLogs(logger, level) as logs: + # We want to test that no log message is emitted + # but assertLogs expects at least one log message. + logging.getLogger(logger).log(level, "dummy") + yield + + level_name = logging.getLevelName(level) + self.assertEqual(logs.output, [f"{level_name}:{logger}:dummy"]) + + def assertDeprecationWarnings(self, recorded_warnings, expected_warnings): + """ + Check recorded deprecation warnings match a list of expected messages. + + """ + self.assertEqual(len(recorded_warnings), len(expected_warnings)) + for recorded, expected in zip(recorded_warnings, expected_warnings): + actual = recorded.message + self.assertEqual(str(actual), expected) + self.assertEqual(type(actual), DeprecationWarning) + + +# Unit for timeouts. May be increased on slow machines by setting the +# WEBSOCKETS_TESTS_TIMEOUT_FACTOR environment variable. +MS = 0.001 * int(os.environ.get("WEBSOCKETS_TESTS_TIMEOUT_FACTOR", 1)) + +# asyncio's debug mode has a 10x performance penalty for this test suite. +if os.environ.get("PYTHONASYNCIODEBUG"): # pragma: no cover + MS *= 10 + +# Ensure that timeouts are larger than the clock's resolution (for Windows). +MS = max(MS, 2.5 * time.get_clock_info("monotonic").resolution) diff --git a/tests/wpt/web-platform-tests/tools/third_party/websockets/tox.ini b/tests/wpt/web-platform-tests/tools/third_party/websockets/tox.ini new file mode 100644 index 00000000000..825e34061ff --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/third_party/websockets/tox.ini @@ -0,0 +1,28 @@ +[tox] +envlist = py36,py37,py38,coverage,black,flake8,isort,mypy + +[testenv] +commands = python -W default -m unittest {posargs} + +[testenv:coverage] +commands = + python -m coverage erase + python -W default -m coverage run -m unittest {posargs} + python -m coverage report --show-missing --fail-under=100 +deps = coverage + +[testenv:black] +commands = black --check src tests +deps = black + +[testenv:flake8] +commands = flake8 src tests +deps = flake8 + +[testenv:isort] +commands = isort --check-only --recursive src tests +deps = isort + +[testenv:mypy] +commands = mypy --strict src +deps = mypy diff --git a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/__init__.py b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/__init__.py index 0e1a9de9a65..a81751407e7 100644 --- a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/__init__.py +++ b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/__init__.py @@ -35,3 +35,5 @@ from .error import ( UnknownMethodException, UnsupportedOperationException, WebDriverException) +from .bidi import ( + BidiSession) diff --git a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/bidi.py b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/bidi.py new file mode 100644 index 00000000000..0bcd489cd35 --- /dev/null +++ b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/bidi.py @@ -0,0 +1,56 @@ +import copy +import websockets + +from . import client + +class BidiSession(client.Session): + def __init__(self, + host, + port, + url_prefix="/", + capabilities=None, + extension=None): + """ + Add a capability of "webSocketUrl": True to enable + Bidirectional connection in session creation. + """ + self.websocket_transport = None + capabilities = self._enable_websocket(capabilities) + super().__init__(host, port, url_prefix, capabilities, extension) + + def _enable_websocket(self, caps): + if caps: + caps.setdefault("alwaysMatch", {}).update({"webSocketUrl": True}) + else: + caps = {"alwaysMatch": {"webSocketUrl": True}} + return caps + + def match(self, capabilities): + """Expensive match to see if capabilities is the same as previously + requested capabilities if websocket would be enabled. + + :return Boolean. + """ + caps = copy.deepcopy(capabilities) + caps = self._enable_websocket(caps) + return super().match(caps) + + async def start(self): + """Start a new WebDriver Bidirectional session + with websocket connected. + + :return: Dictionary with `capabilities` and `sessionId`. + """ + value = super().start() + + if not self.websocket_transport or not self.websocket_transport.open: + self.websocket_transport = await websockets.connect(self.capabilities["webSocketUrl"]) + return value + + async def end(self): + """Close websocket connection first before closing session. + """ + if self.websocket_transport: + await self.websocket_transport.close() + self.websocket_transport = None + super().end() diff --git a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py index 19fe336a6e1..22533409ffd 100644 --- a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py +++ b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py @@ -2,8 +2,7 @@ from . import error from . import protocol from . import transport -from six import string_types -from six.moves.urllib import parse as urlparse +from urllib import parse as urlparse def command(func): @@ -435,7 +434,7 @@ class Cookies(object): cookie = {"name": name, "value": None} - if isinstance(name, string_types): + if isinstance(name, str): cookie["value"] = value elif hasattr(value, "value"): cookie["value"] = value.value @@ -506,6 +505,9 @@ class Session(object): def __del__(self): self.end() + def match(self, capabilities): + return self.requested_capabilities == capabilities + def start(self): """Start a new WebDriver session. @@ -753,7 +755,6 @@ class Session(object): def screenshot(self): return self.send_session_command("GET", "screenshot") - class Element(object): """ Representation of a web element. diff --git a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/error.py b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/error.py index 642612985a8..807c5925e14 100644 --- a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/error.py +++ b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/error.py @@ -1,8 +1,6 @@ import collections import json -from six import itervalues - class WebDriverException(Exception): http_status = None @@ -220,6 +218,6 @@ def get(error_code): _errors = collections.defaultdict() -for item in list(itervalues(locals())): +for item in list(locals().values()): if type(item) == type and issubclass(item, WebDriverException): _errors[item.status_code] = item diff --git a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/protocol.py b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/protocol.py index d3faa8508b0..e71e01d6208 100644 --- a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/protocol.py +++ b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/protocol.py @@ -2,8 +2,6 @@ import json import webdriver -from six import iteritems - """WebDriver wire protocol codecs.""" @@ -45,5 +43,5 @@ class Decoder(json.JSONDecoder): elif isinstance(payload, dict) and webdriver.ShadowRoot.identifier in payload: return webdriver.ShadowRoot.from_json(payload, self.session) elif isinstance(payload, dict): - return {k: self.object_hook(v) for k, v in iteritems(payload)} + return {k: self.object_hook(v) for k, v in payload.items()} return payload diff --git a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/transport.py b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/transport.py index 33142d30fce..15ba6b8fee2 100644 --- a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/transport.py +++ b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/transport.py @@ -1,10 +1,9 @@ import json import select -from six import text_type, PY3 -from six.moves.collections_abc import Mapping -from six.moves.http_client import HTTPConnection -from six.moves.urllib import parse as urlparse +from collections.abc import Mapping +from http.client import HTTPConnection +from urllib import parse as urlparse from . import error @@ -157,8 +156,6 @@ class HTTPWireProtocol(object): """Gets the current HTTP connection, or lazily creates one.""" if not self._conn: conn_kwargs = {} - if not PY3: - conn_kwargs["strict"] = True # We are not setting an HTTP timeout other than the default when the # connection its created. The send method has a timeout value if needed. self._conn = HTTPConnection(self.host, self.port, **conn_kwargs) @@ -240,7 +237,7 @@ class HTTPWireProtocol(object): return Response.from_http(response, decoder=decoder, **codec_kwargs) def _request(self, method, uri, payload, headers=None, timeout=None): - if isinstance(payload, text_type): + if isinstance(payload, str): payload = payload.encode("utf-8") if headers is None: diff --git a/tests/wpt/web-platform-tests/tools/wpt/browser.py b/tests/wpt/web-platform-tests/tools/wpt/browser.py index 800080e91e6..2995d52dcad 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/browser.py +++ b/tests/wpt/web-platform-tests/tools/wpt/browser.py @@ -9,7 +9,7 @@ from abc import ABCMeta, abstractmethod from datetime import datetime, timedelta from distutils.spawn import find_executable -from six.moves.urllib.parse import urlsplit +from urllib.parse import urlsplit import requests from .utils import call, get, rmtree, untar, unzip, get_download_to_descriptor, sha256sum diff --git a/tests/wpt/web-platform-tests/tools/wpt/run.py b/tests/wpt/web-platform-tests/tools/wpt/run.py index 68f8e32ca18..1dcb1d462ee 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/run.py +++ b/tests/wpt/web-platform-tests/tools/wpt/run.py @@ -3,7 +3,6 @@ import os import platform import sys from distutils.spawn import find_executable -from six.moves import input wpt_root = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) sys.path.insert(0, os.path.abspath(os.path.join(wpt_root, "tools"))) @@ -757,9 +756,8 @@ def setup_logging(kwargs, default_config=None, formatter_defaults=None): def setup_wptrunner(venv, **kwargs): from wptrunner import wptcommandline - from six import iteritems - kwargs = utils.Kwargs(iteritems(kwargs)) + kwargs = utils.Kwargs(kwargs.items()) kwargs["product"] = kwargs["product"].replace("-", "_") diff --git a/tests/wpt/web-platform-tests/tools/wpt/testfiles.py b/tests/wpt/web-platform-tests/tools/wpt/testfiles.py index fee19cd1c49..5c398588174 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/testfiles.py +++ b/tests/wpt/web-platform-tests/tools/wpt/testfiles.py @@ -6,7 +6,7 @@ import subprocess import sys from collections import OrderedDict -from six import ensure_text, ensure_str, iteritems +from six import ensure_text, ensure_str try: from ..manifest import manifest @@ -98,7 +98,7 @@ def branch_point(): branch_point = None # if there are any commits, take the first parent that is not in commits - for commit, parents in iteritems(commit_parents): + for commit, parents in commit_parents.items(): for parent in parents: if parent not in commit_parents: branch_point = parent @@ -251,7 +251,7 @@ def affected_testfiles(files_changed, # type: Iterable[Text] nontests_changed = set(files_changed) wpt_manifest = load_manifest(manifest_path, manifest_update) - test_types = ["crashtest", "testharness", "reftest", "wdspec"] + test_types = ["crashtest", "print-reftest", "reftest", "testharness", "wdspec"] support_files = {os.path.join(wpt_root, path) for _, path, _ in wpt_manifest.itertypes("support")} wdspec_test_files = {os.path.join(wpt_root, path) diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py index 0ecc6d97c80..591b0283e85 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py @@ -1,15 +1,10 @@ import logging import inspect import mock -import os import pytest import subprocess import sys -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - from tools.wpt import browser diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_install.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_install.py index 419175fc019..d1abbed142e 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_install.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_install.py @@ -4,10 +4,6 @@ import sys import pytest -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - from tools.wpt import browser, utils, wpt diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_markdown.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_markdown.py index 582f72de69e..dec9ad9c489 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_markdown.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_markdown.py @@ -1,10 +1,3 @@ -import os -import sys - -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - from tools.wpt import markdown def test_format_comment_title(): diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_revlist.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_revlist.py index b99df68b48e..30208f4cf68 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_revlist.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_revlist.py @@ -1,11 +1,4 @@ import mock -import os -import sys - -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - from tools.wpt import revlist diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_run.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_run.py index daf70bb9470..d533977e0c7 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_run.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_run.py @@ -1,15 +1,10 @@ import mock -import os import tempfile import shutil import sys import pytest -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - from tools.wpt import run from tools import localpaths # noqa: F401 from wptrunner.browsers import product_list diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_testfiles.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_testfiles.py index f93fe8d8ba1..c4d40ff9fd0 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_testfiles.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_testfiles.py @@ -1,10 +1,5 @@ import os.path from mock import patch -import sys - -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) from tools.manifest.manifest import Manifest from tools.wpt import testfiles diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_wpt.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_wpt.py index ee338a33f83..eb0377760c7 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_wpt.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_wpt.py @@ -15,10 +15,7 @@ except ImportError: import pytest -here = os.path.dirname(__file__) -root = os.path.abspath(os.path.join(here, "..", "..", "..")) -sys.path.insert(0, root) - +here = os.path.abspath(os.path.dirname(__file__)) from tools.wpt import utils, wpt diff --git a/tests/wpt/web-platform-tests/tools/wpt/utils.py b/tests/wpt/web-platform-tests/tools/wpt/utils.py index 61dcda5470c..30e9574ec0a 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/utils.py +++ b/tests/wpt/web-platform-tests/tools/wpt/utils.py @@ -9,7 +9,7 @@ import time import zipfile from io import BytesIO from socket import error as SocketError # NOQA: N812 -from six.moves.urllib.request import urlopen +from urllib.request import urlopen MYPY = False if MYPY: diff --git a/tests/wpt/web-platform-tests/tools/wpt/virtualenv.py b/tests/wpt/web-platform-tests/tools/wpt/virtualenv.py index 51b97cead8d..1cfb1506cfb 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/virtualenv.py +++ b/tests/wpt/web-platform-tests/tools/wpt/virtualenv.py @@ -55,13 +55,9 @@ class Virtualenv(object): @property def pip_path(self): - if sys.version_info.major >= 3: - pip_executable = "pip3" - else: - pip_executable = "pip2" - path = find_executable(pip_executable, self.bin_path) + path = find_executable("pip3", self.bin_path) if path is None: - raise ValueError("%s not found" % pip_executable) + raise ValueError("pip3 not found") return path @property diff --git a/tests/wpt/web-platform-tests/tools/wpt/wpt.py b/tests/wpt/web-platform-tests/tools/wpt/wpt.py index d47c91ce316..1faf7d7af73 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/wpt.py +++ b/tests/wpt/web-platform-tests/tools/wpt/wpt.py @@ -6,7 +6,6 @@ import sys from tools import localpaths # noqa: F401 -from six import iteritems from . import virtualenv @@ -23,7 +22,7 @@ def load_commands(): base_dir = os.path.dirname(abs_path) with open(abs_path, "r") as f: data = json.load(f) - for command, props in iteritems(data): + for command, props in data.items(): assert "path" in props assert "script" in props rv[command] = { @@ -31,7 +30,6 @@ def load_commands(): "script": props["script"], "parser": props.get("parser"), "parse_known": props.get("parse_known", False), - "py3only": props.get("py3only", False), "help": props.get("help"), "virtualenv": props.get("virtualenv", True), "install": props.get("install", []), @@ -51,7 +49,7 @@ def parse_args(argv, commands=load_commands()): help="Whether to use the virtualenv as-is. Must set --venv as well") parser.add_argument("--debug", action="store_true", help="Run the debugger in case of an exception") subparsers = parser.add_subparsers(dest="command") - for command, props in iteritems(commands): + for command, props in commands.items(): subparsers.add_parser(command, help=props["help"], add_help=False) args, extra = parser.parse_known_args(argv) @@ -96,8 +94,7 @@ def create_complete_parser(): for command in commands: props = commands[command] - if (props["virtualenv"] and - (not props["py3only"] or sys.version_info.major == 3)): + if props["virtualenv"]: setup_virtualenv(None, False, props) subparser = import_command('wpt', command, props)[1] diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/requirements_safari.txt b/tests/wpt/web-platform-tests/tools/wptrunner/requirements_safari.txt index 874980b338a..55b474ba914 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/requirements_safari.txt +++ b/tests/wpt/web-platform-tests/tools/wptrunner/requirements_safari.txt @@ -1 +1,2 @@ mozprocess==1.2.1 +psutil==5.8.0 diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/test/test.py b/tests/wpt/web-platform-tests/tools/wptrunner/test/test.py index 42e7abbbab4..fd33c9ae2ea 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/test/test.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/test/test.py @@ -3,11 +3,11 @@ import argparse import os import sys +from configparser import ConfigParser + from mozlog import structuredlog from mozlog.handlers import BaseHandler, StreamHandler from mozlog.formatters import MachFormatter -from six import iteritems -from six.moves.configparser import ConfigParser from wptrunner import wptcommandline, wptrunner here = os.path.abspath(os.path.dirname(__file__)) @@ -84,7 +84,7 @@ def run_tests(product, kwargs): def settings_to_argv(settings): rv = [] - for name, value in iteritems(settings): + for name, value in settings.items(): key = "--%s" % name if not value: rv.append(key) @@ -110,7 +110,7 @@ def run(config, args): logger.suite_start(tests=[]) - for product, product_settings in iteritems(config["products"]): + for product, product_settings in config["products"].items(): if args.product and product not in args.product: continue diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/android_webview.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/android_webview.py index 918a881b908..b40f8b90c03 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/android_webview.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/android_webview.py @@ -49,10 +49,11 @@ def executor_kwargs(logger, test_type, server_config, cache_manager, run_info_da # Note that for WebView, we launch a test shell and have the test shell use WebView. # https://chromium.googlesource.com/chromium/src/+/HEAD/android_webview/docs/webview-shell.md capabilities["goog:chromeOptions"]["androidPackage"] = \ - "org.chromium.webview_shell" - capabilities["goog:chromeOptions"]["androidActivity"] = ".WebPlatformTestsActivity" - if kwargs.get('device_serial'): - capabilities["goog:chromeOptions"]["androidDeviceSerial"] = kwargs['device_serial'] + kwargs.get("package_name", "org.chromium.webview_shell") + capabilities["goog:chromeOptions"]["androidActivity"] = \ + "org.chromium.webview_shell.WebPlatformTestsActivity" + if kwargs.get("device_serial"): + capabilities["goog:chromeOptions"]["androidDeviceSerial"] = kwargs["device_serial"] # Workaround: driver.quit() cannot quit SystemWebViewShell. executor_kwargs["pause_after_test"] = False diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/base.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/base.py index f2205b160f6..dd6621cb72b 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/base.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/base.py @@ -3,7 +3,6 @@ import platform import socket from abc import ABCMeta, abstractmethod from copy import deepcopy -from six import iteritems from ..wptcommandline import require_arg # noqa: F401 @@ -202,5 +201,5 @@ class ExecutorBrowser(object): up the browser from the runner process. """ def __init__(self, **kwargs): - for k, v in iteritems(kwargs): + for k, v in kwargs.items(): setattr(self, k, v) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/safari.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/safari.py index 0f62639c635..9565f1c302f 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/safari.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/safari.py @@ -1,3 +1,9 @@ +import os +import plistlib +from distutils.spawn import find_executable + +import psutil + from .base import Browser, ExecutorBrowser, require_arg from .base import get_timeout_multiplier # noqa: F401 from ..webdriver_server import SafariDriverServer @@ -28,7 +34,8 @@ def check_args(**kwargs): def browser_kwargs(logger, test_type, run_info_data, config, **kwargs): return {"webdriver_binary": kwargs["webdriver_binary"], - "webdriver_args": kwargs.get("webdriver_args")} + "webdriver_args": kwargs.get("webdriver_args"), + "kill_safari": kwargs.get("kill_safari", False)} def executor_kwargs(logger, test_type, server_config, cache_manager, run_info_data, @@ -58,15 +65,60 @@ class SafariBrowser(Browser): ``wptrunner.webdriver.SafariDriverServer``. """ - def __init__(self, logger, webdriver_binary, webdriver_args=None, **kwargs): + def __init__(self, logger, webdriver_binary, webdriver_args=None, kill_safari=False, **kwargs): """Creates a new representation of Safari. The `webdriver_binary` argument gives the WebDriver binary to use for testing. (The browser binary location cannot be specified, as Safari and SafariDriver are - coupled.)""" + coupled.) If `kill_safari` is True, then `Browser.stop` will stop Safari.""" Browser.__init__(self, logger) self.server = SafariDriverServer(self.logger, binary=webdriver_binary, args=webdriver_args) + if "/" not in webdriver_binary: + wd_path = find_executable(webdriver_binary) + else: + wd_path = webdriver_binary + self.safari_path = self._findAssociatedSafariExecutable(wd_path) + + logger.debug("WebDriver executable path: %s" % wd_path) + logger.debug("Safari executable path: %s" % self.safari_path) + + self.kill_safari = kill_safari + + def _findAssociatedSafariExecutable(self, wd_path): + bundle_paths = [ + os.path.join(os.path.dirname(wd_path), "..", ".."), # bundled Safari (e.g. STP) + os.path.join(os.path.dirname(wd_path), "Safari.app"), # local Safari build + "/Applications/Safari.app", # system Safari + ] + + for bundle_path in bundle_paths: + info_path = os.path.join(bundle_path, "Contents", "Info.plist") + if not os.path.isfile(info_path): + continue + + with open(info_path, "rb") as fp: + info = plistlib.load(fp) + + # check we have a Safari family bundle + if "CFBundleIdentifier" not in info: + continue + ident = info["CFBundleIdentifier"] + if not isinstance(ident, str) or not ident.startswith("com.apple.Safari"): + continue + + # get the executable name + if "CFBundleExecutable" not in info: + continue + exe = info["CFBundleExecutable"] + if not isinstance(exe, str): + continue + + exe_path = os.path.join(bundle_path, "Contents", "MacOS", exe) + if not os.path.isfile(exe_path): + continue + + return exe_path def start(self, **kwargs): self.server.start(block=False) @@ -74,6 +126,20 @@ class SafariBrowser(Browser): def stop(self, force=False): self.server.stop(force=force) + if self.kill_safari: + self.logger.debug("Going to stop Safari") + for proc in psutil.process_iter(attrs=["exe"]): + if proc.info["exe"] is not None and os.path.samefile(proc.info["exe"], self.safari_path): + self.logger.debug("Stopping Safari %s" % proc.pid) + try: + proc.terminate() + try: + proc.wait(10) + except psutil.TimeoutExpired: + proc.kill() + except psutil.NoSuchProcess: + pass + def pid(self): return self.server.pid diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/sauce.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/sauce.py index 483bf206172..99ece89d454 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/sauce.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/sauce.py @@ -12,7 +12,7 @@ import time import requests -from six.moves import cStringIO as StringIO +from io import StringIO from .base import Browser, ExecutorBrowser, require_arg from .base import get_timeout_multiplier # noqa: F401 diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/config.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/config.py index d46beb8e71d..3f5e9341dcf 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/config.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/config.py @@ -1,4 +1,4 @@ -from six.moves.configparser import SafeConfigParser +from configparser import SafeConfigParser import os import sys from collections import OrderedDict diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/environment.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/environment.py index b68fce0557a..86dcb93def1 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/environment.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/environment.py @@ -5,7 +5,6 @@ import signal import socket import sys import time -from six import iteritems from mozlog import get_default_logger, handlers, proxy @@ -108,7 +107,7 @@ class TestEnvironment(object): def __exit__(self, exc_type, exc_val, exc_tb): self.process_interrupts() - for scheme, servers in iteritems(self.servers): + for scheme, servers in self.servers.items(): for port, server in servers: server.kill() for cm in self.env_extras_cms: @@ -215,7 +214,7 @@ class TestEnvironment(object): route_builder.add_handler("GET", "/resources/testdriver.js", StringHandler(data, "text/javascript")) - for url_base, paths in iteritems(self.test_paths): + for url_base, paths in self.test_paths.items(): if url_base == "/": continue route_builder.add_mount_point(url_base, paths["tests_path"]) @@ -247,13 +246,13 @@ class TestEnvironment(object): failed = [] pending = [] host = self.config["server_host"] - for scheme, servers in iteritems(self.servers): + for scheme, servers in self.servers.items(): for port, server in servers: if not server.is_alive(): failed.append((scheme, port)) if not failed and self.test_server_port: - for scheme, servers in iteritems(self.servers): + for scheme, servers in self.servers.items(): # TODO(Hexcles): Find a way to test QUIC's UDP port. if scheme == "quic-transport": continue diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py index 1b75421941d..68ba04e1a68 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py @@ -8,9 +8,8 @@ import traceback import socket import sys from abc import ABCMeta, abstractmethod -from six import text_type -from six.moves.http_client import HTTPConnection -from six.moves.urllib.parse import urljoin, urlsplit, urlunsplit +from http.client import HTTPConnection +from urllib.parse import urljoin, urlsplit, urlunsplit from .actions import actions from .protocol import Protocol, BaseProtocolPart @@ -333,7 +332,7 @@ class TestExecutor(object): status = e.status else: status = "INTERNAL-ERROR" - message = text_type(getattr(e, "message", "")) + message = str(getattr(e, "message", "")) if message: message += "\n" message += exception_string diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorchrome.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorchrome.py index 3a704754137..431d398ebd2 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorchrome.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorchrome.py @@ -1,7 +1,7 @@ import os import traceback -from six.moves.urllib.parse import urljoin +from urllib.parse import urljoin from .base import WdspecProtocol, WdspecExecutor, get_pages from .executorwebdriver import WebDriverProtocol, WebDriverRefTestExecutor, WebDriverRun diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py index d61e1f262cd..81be731b06d 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py @@ -5,8 +5,7 @@ import time import traceback import uuid -from six import iteritems, iterkeys -from six.moves.urllib.parse import urljoin +from urllib.parse import urljoin errors = None marionette = None @@ -727,14 +726,14 @@ class MarionetteProtocol(Protocol): def on_environment_change(self, old_environment, new_environment): #Unset all the old prefs - for name in iterkeys(old_environment.get("prefs", {})): + for name in old_environment.get("prefs", {}).keys(): value = self.executor.original_pref_values[name] if value is None: self.prefs.clear(name) else: self.prefs.set(name, value) - for name, value in iteritems(new_environment.get("prefs", {})): + for name, value in new_environment.get("prefs", {}).items(): self.executor.original_pref_values[name] = self.prefs.get(name) self.prefs.set(name, value) @@ -1000,7 +999,7 @@ class MarionetteRefTestExecutor(RefTestExecutor): result["extra"]["assertion_count"] = assertion_count if self.debug_test and result["status"] in ["PASS", "FAIL", "ERROR"] and "extra" in result: - self.parent.base.set_window(self.parent.base.window_handles()[0]) + self.protocol.base.set_window(self.protocol.base.window_handles()[0]) self.protocol.debug.load_reftest_analyzer(test, result) return self.convert_result(test, result) @@ -1048,8 +1047,7 @@ class InternalRefTestImplementation(RefTestImplementation): data = {"screenshot": screenshot, "isPrint": self.executor.is_print} if self.executor.group_metadata is not None: data["urlCount"] = {urljoin(self.executor.server_url(key[0]), key[1]):value - for key, value in iteritems( - self.executor.group_metadata.get("url_count", {})) + for key, value in self.executor.group_metadata.get("url_count", {}).items() if value > 1} self.chrome_scope = chrome_scope if chrome_scope: diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorselenium.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorselenium.py index 6070007b21a..783903b9bbe 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorselenium.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorselenium.py @@ -6,7 +6,7 @@ import threading import time import traceback import uuid -from six.moves.urllib.parse import urljoin +from urllib.parse import urljoin from .base import (CallbackHandler, RefTestExecutor, diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservo.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservo.py index efe93abc69c..597c879cd14 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservo.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservo.py @@ -7,7 +7,7 @@ import tempfile import threading import traceback import uuid -from six import ensure_str, iteritems +from six import ensure_str from mozprocess import ProcessHandler @@ -48,7 +48,7 @@ def build_servo_command(test, test_url_func, browser, binary, pause_after_test, args += ["-Z", debug_opts] for stylesheet in browser.user_stylesheets: args += ["--user-stylesheet", stylesheet] - for pref, value in iteritems(test.environment.get('prefs', {})): + for pref, value in test.environment.get('prefs', {}).items(): args += ["--pref", "%s=%s" % (pref, value)] if browser.ca_certificate_path: args += ["--certificate-path", browser.ca_certificate_path] diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py index 0a968bf3f8c..a04a55a6f00 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py @@ -6,7 +6,7 @@ import threading import time import traceback import uuid -from six.moves.urllib.parse import urljoin +from urllib.parse import urljoin from .base import (CallbackHandler, CrashtestExecutor, diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py index 05e251c7841..ab16b0efd0a 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py @@ -531,7 +531,7 @@ class DebugProtocolPart(ProtocolPart): def load_reftest_analyzer(self, test, result): import io import mozlog - from six.moves.urllib.parse import quote, urljoin + from urllib.parse import quote, urljoin debug_test_logger = mozlog.structuredlog.StructuredLogger("debug_test") output = io.StringIO() diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/expectedtree.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/expectedtree.py index 7521f25b134..76ade95f07f 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/expectedtree.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/expectedtree.py @@ -1,6 +1,5 @@ from math import log from collections import defaultdict -from six import iteritems, itervalues class Node(object): def __init__(self, prop, value): @@ -34,7 +33,7 @@ def entropy(results): result_counts = defaultdict(int) total = float(len(results)) - for values in itervalues(results): + for values in results.values(): # Not sure this is right, possibly want to treat multiple values as # distinct from multiple of the same value? for value in values: @@ -42,7 +41,7 @@ def entropy(results): entropy_sum = 0 - for count in itervalues(result_counts): + for count in result_counts.values(): prop = float(count) / total entropy_sum -= prop * log(prop, 2) @@ -53,7 +52,7 @@ def split_results(prop, results): """Split a dictionary of results into a dictionary of dictionaries where each sub-dictionary has a specific value of the given property""" by_prop = defaultdict(dict) - for run_info, value in iteritems(results): + for run_info, value in results.items(): by_prop[run_info[prop]][run_info] = value return by_prop @@ -78,13 +77,13 @@ def build_tree(properties, dependent_props, results, tree=None): prop_index = {prop: i for i, prop in enumerate(properties)} all_results = defaultdict(int) - for result_values in itervalues(results): - for result_value, count in iteritems(result_values): + for result_values in results.values(): + for result_value, count in result_values.items(): all_results[result_value] += count # If there is only one result we are done if not properties or len(all_results) == 1: - for value, count in iteritems(all_results): + for value, count in all_results.items(): tree.result_values[value] += count tree.run_info |= set(results.keys()) return tree @@ -100,7 +99,7 @@ def build_tree(properties, dependent_props, results, tree=None): continue new_entropy = 0. results_sets_entropy = [] - for prop_value, result_set in iteritems(result_sets): + for prop_value, result_set in result_sets.items(): results_sets_entropy.append((entropy(result_set), prop_value, result_set)) new_entropy += (float(len(result_set)) / len(results)) * results_sets_entropy[-1][0] @@ -110,7 +109,7 @@ def build_tree(properties, dependent_props, results, tree=None): # In the case that no properties partition the space if not results_partitions: - for value, count in iteritems(all_results): + for value, count in all_results.items(): tree.result_values[value] += count tree.run_info |= set(results.keys()) return tree diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/chromium.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/chromium.py index b6cbb203a31..ae8d96a1170 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/chromium.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/chromium.py @@ -1,6 +1,5 @@ import json import time -import six from collections import defaultdict from mozlog.formatters import base @@ -88,7 +87,7 @@ class ChromiumFormatter(base.BaseFormatter): :param str artifact_name: the name of the artifact :param str artifact_value: the value of the artifact """ - assert isinstance(artifact_value, six.string_types), "artifact_value must be a str" + assert isinstance(artifact_value, str), "artifact_value must be a str" if "artifacts" not in cur_dict.keys(): cur_dict["artifacts"] = defaultdict(list) cur_dict["artifacts"][artifact_name].append(artifact_value) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py index 55a12b1d774..53d64df70f0 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py @@ -1,7 +1,7 @@ import json import sys from os.path import dirname, join -from six.moves import cStringIO as StringIO +from io import StringIO from mozlog import handlers, structuredlog diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/instruments.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/instruments.py index a887cf4fda7..4e3e013aec4 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/instruments.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/instruments.py @@ -1,6 +1,6 @@ import time import threading -from six.moves.queue import Queue +from queue import Queue """Instrumentation for measuring high-level time spent on various tasks inside the runner. diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestexpected.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestexpected.py index 31c57e9cb73..2f7f5339b6d 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestexpected.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestexpected.py @@ -1,8 +1,6 @@ import os from collections import deque -from six import string_types, text_type -from six.moves.urllib.parse import urljoin - +from urllib.parse import urljoin from .wptmanifest.backends import static from .wptmanifest.backends.base import ManifestItem @@ -49,7 +47,7 @@ def list_prop(name, node): """List property""" try: list_prop = node.get(name) - if isinstance(list_prop, string_types): + if isinstance(list_prop, str): return [list_prop] return list(list_prop) except KeyError: @@ -59,7 +57,7 @@ def list_prop(name, node): def str_prop(name, node): try: prop = node.get(name) - if not isinstance(prop, string_types): + if not isinstance(prop, str): raise ValueError return prop except KeyError: @@ -70,7 +68,7 @@ def tags(node): """Set of tags that have been applied to the test""" try: value = node.get("tags") - if isinstance(value, text_type): + if isinstance(value, str): return {value} return set(value) except KeyError: @@ -79,7 +77,7 @@ def tags(node): def prefs(node): def value(ini_value): - if isinstance(ini_value, text_type): + if isinstance(ini_value, str): return tuple(pref_piece.strip() for pref_piece in ini_value.split(':', 1)) else: # this should be things like @Reset, which are apparently type 'object' @@ -87,7 +85,7 @@ def prefs(node): try: node_prefs = node.get("prefs") - if isinstance(node_prefs, text_type): + if isinstance(node_prefs, str): rv = dict(value(node_prefs)) else: rv = dict(value(item) for item in node_prefs) @@ -99,7 +97,7 @@ def prefs(node): def set_prop(name, node): try: node_items = node.get(name) - if isinstance(node_items, text_type): + if isinstance(node_items, str): rv = {node_items} else: rv = set(node_items) @@ -112,7 +110,7 @@ def leak_threshold(node): rv = {} try: node_items = node.get("leak-threshold") - if isinstance(node_items, text_type): + if isinstance(node_items, str): node_items = [node_items] for item in node_items: process, value = item.rsplit(":", 1) @@ -169,7 +167,7 @@ def fuzzy_prop(node): if not isinstance(value, list): value = [value] for item in value: - if not isinstance(item, text_type): + if not isinstance(item, str): rv.append(item) continue parts = item.rsplit(":", 1) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestinclude.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestinclude.py index 79b5b19b3a3..97348e6d52f 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestinclude.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestinclude.py @@ -6,8 +6,7 @@ be included or excluded. """ import glob import os -from six import iteritems -from six.moves.urllib.parse import urlparse, urlsplit +from urllib.parse import urlparse, urlsplit from .wptmanifest.node import DataNode from .wptmanifest.backends import conditional @@ -95,7 +94,7 @@ class IncludeManifest(ManifestItem): if paths: urls = [] for path in paths: - for manifest, data in iteritems(test_manifests): + for manifest, data in test_manifests.items(): found = False rel_path = os.path.relpath(path, data["tests_path"]) iterator = manifest.iterpath if os.path.isfile(path) else manifest.iterdir diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestupdate.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestupdate.py index c74632b0486..5bccaa2d2a1 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestupdate.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/manifestupdate.py @@ -1,9 +1,8 @@ from __future__ import print_function import os -from six.moves.urllib.parse import urljoin, urlsplit +from urllib.parse import urljoin, urlsplit from collections import namedtuple, defaultdict, deque from math import ceil -from six import integer_types, iterkeys, itervalues, iteritems, string_types, text_type from .wptmanifest import serialize from .wptmanifest.node import (DataNode, ConditionalNode, BinaryExpressionNode, @@ -76,7 +75,7 @@ class UpdateProperties(object): return name in self._classes def __iter__(self): - for name in iterkeys(self._classes): + for name in self._classes.keys(): yield getattr(self, name) @@ -313,8 +312,8 @@ def build_conditional_tree(_, run_info_properties, results): def build_unconditional_tree(_, run_info_properties, results): root = expectedtree.Node(None, None) - for run_info, values in iteritems(results): - for value, count in iteritems(values): + for run_info, values in results.items(): + for value, count in values.items(): root.result_values[value] += count root.run_info.add(run_info) return root @@ -411,7 +410,7 @@ class PropertyUpdate(object): for e in errors: if disable_intermittent: condition = e.cond.children[0] if e.cond else None - msg = disable_intermittent if isinstance(disable_intermittent, string_types+(text_type,)) else "unstable" + msg = disable_intermittent if isinstance(disable_intermittent, str) else "unstable" self.node.set("disabled", msg, condition) self.node.new_disabled = True else: @@ -499,7 +498,7 @@ class PropertyUpdate(object): for run_info in node.run_info} node_by_run_info = {run_info: node - for (run_info, node) in iteritems(run_info_index) + for (run_info, node) in run_info_index.items() if node.result_values} run_info_by_condition = self.run_info_by_condition(run_info_index, @@ -512,7 +511,7 @@ class PropertyUpdate(object): # using the properties we've specified and not matching any run_info top_level_props, dependent_props = self.node.root.run_info_properties update_properties = set(top_level_props) - for item in itervalues(dependent_props): + for item in dependent_props.values(): update_properties |= set(item) for condition in current_conditions: if ((not condition.variables.issubset(update_properties) and @@ -695,7 +694,7 @@ class ExpectedUpdate(PropertyUpdate): raise ConditionError counts = {} - for status, count in iteritems(new): + for status, count in new.items(): if isinstance(status, tuple): counts[status[0]] = count counts.update({intermittent: 0 for intermittent in status[1:] if intermittent not in counts}) @@ -709,9 +708,9 @@ class ExpectedUpdate(PropertyUpdate): # Counts with 0 are considered intermittent. statuses = ["OK", "PASS", "FAIL", "ERROR", "TIMEOUT", "CRASH"] status_priority = {value: i for i, value in enumerate(statuses)} - sorted_new = sorted(iteritems(counts), key=lambda x:(-1 * x[1], - status_priority.get(x[0], - len(status_priority)))) + sorted_new = sorted(counts.items(), key=lambda x:(-1 * x[1], + status_priority.get(x[0], + len(status_priority)))) expected = [] for status, count in sorted_new: # If we are not removing existing recorded intermittents, with a count of 0, @@ -777,7 +776,7 @@ class AppendOnlyListUpdate(PropertyUpdate): for item in new: if item is None: continue - elif isinstance(item, text_type): + elif isinstance(item, str): rv.add(item) else: rv |= item @@ -825,7 +824,7 @@ class LeakThresholdUpdate(PropertyUpdate): return result def to_ini_value(self, data): - return ["%s:%s" % item for item in sorted(iteritems(data))] + return ["%s:%s" % item for item in sorted(data.items())] def from_ini_value(self, data): rv = {} @@ -900,10 +899,10 @@ def make_expr(prop_set, rhs): def make_node(value): - if isinstance(value, integer_types+(float,)): + if isinstance(value, (int, float,)): node = NumberNode(value) - elif isinstance(value, text_type): - node = StringNode(text_type(value)) + elif isinstance(value, str): + node = StringNode(str(value)) elif hasattr(value, "__iter__"): node = ListNode() for item in value: @@ -912,10 +911,10 @@ def make_node(value): def make_value_node(value): - if isinstance(value, integer_types+(float,)): + if isinstance(value, (int, float,)): node = ValueNode(value) - elif isinstance(value, text_type): - node = ValueNode(text_type(value)) + elif isinstance(value, str): + node = ValueNode(str(value)) elif hasattr(value, "__iter__"): node = ListNode() for item in value: diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/metadata.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/metadata.py index ab8d4740f3f..ddc433d8aa9 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/metadata.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/metadata.py @@ -5,8 +5,8 @@ import sys from collections import defaultdict, namedtuple from mozlog import structuredlog -from six import ensure_str, ensure_text, iteritems, iterkeys, itervalues, text_type -from six.moves import intern, range +from six import ensure_str, ensure_text +from sys import intern from . import manifestupdate from . import testloader @@ -45,11 +45,11 @@ class RunInfo(object): return self.canonical_repr == other.canonical_repr def iteritems(self): - for key, value in iteritems(self.data): + for key, value in self.data.items(): yield key, value def items(self): - return list(iteritems(self)) + return list(self.items()) def update_expected(test_paths, serve_root, log_file_names, @@ -129,7 +129,7 @@ def unexpected_changes(manifests, change_data, files_changed): files_changed = set(files_changed) root_manifest = None - for manifest, paths in iteritems(manifests): + for manifest, paths in manifests.items(): if paths["url_base"] == "/": root_manifest = manifest break @@ -240,7 +240,7 @@ def pack_result(data): def unpack_result(data): if isinstance(data, int): return (status_intern.get(data), None) - if isinstance(data, text_type): + if isinstance(data, str): return (data, None) # Unpack multiple statuses into a tuple to be used in the Results named tuple below, # separating `status` and `known_intermittent`. @@ -259,7 +259,7 @@ def load_test_data(test_paths): manifests = manifest_loader.load() id_test_map = {} - for test_manifest, paths in iteritems(manifests): + for test_manifest, paths in manifests.items(): id_test_map.update(create_test_tree(paths["metadata_path"], test_manifest)) return id_test_map @@ -287,10 +287,10 @@ def update_results(id_test_map, disable_intermittent, update_intermittent, remove_intermittent): - test_file_items = set(itervalues(id_test_map)) + test_file_items = set(id_test_map.values()) default_expected_by_type = {} - for test_type, test_cls in iteritems(wpttest.manifest_test_cls): + for test_type, test_cls in wpttest.manifest_test_cls.items(): if test_cls.result_cls: default_expected_by_type[(test_type, False)] = test_cls.result_cls.default_expected if test_cls.subtest_result_cls: @@ -431,7 +431,7 @@ class ExpectedUpdater(object): action_map["lsan_leak"](item) mozleak_data = data.get("mozleak", {}) - for scope, scope_data in iteritems(mozleak_data): + for scope, scope_data in mozleak_data.items(): for key, action in [("objects", "mozleak_object"), ("total", "mozleak_total")]: for item in scope_data.get(key, []): @@ -668,11 +668,11 @@ class TestFileData(object): # Return subtest nodes present in the expected file, but missing from the data rv = [] - for test_id, subtests in iteritems(self.data): + for test_id, subtests in self.data.items(): test = expected.get_test(ensure_text(test_id)) if not test: continue - seen_subtests = set(ensure_text(item) for item in iterkeys(subtests) if item is not None) + seen_subtests = set(ensure_text(item) for item in subtests.keys() if item is not None) missing_subtests = set(test.subtests.keys()) - seen_subtests for item in missing_subtests: expected_subtest = test.get_subtest(item) @@ -691,7 +691,7 @@ class TestFileData(object): # since removing these may be inappropriate top_level_props, dependent_props = update_properties all_properties = set(top_level_props) - for item in itervalues(dependent_props): + for item in dependent_props.values(): all_properties |= set(item) filtered = [] @@ -741,9 +741,9 @@ class TestFileData(object): test_expected = expected.get_test(test_id) expected_by_test[test_id] = test_expected - for test_id, test_data in iteritems(self.data): + for test_id, test_data in self.data.items(): test_id = ensure_str(test_id) - for subtest_id, results_list in iteritems(test_data): + for subtest_id, results_list in test_data.items(): for prop, run_info, value in results_list: # Special case directory metadata if subtest_id is None and test_id.endswith("__dir__"): diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/mpcontext.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/mpcontext.py index daade1054cf..a50c04782a6 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/mpcontext.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/mpcontext.py @@ -1,21 +1,11 @@ import multiprocessing -import six - _context = None -class MpContext(object): - def __getattr__(self, name): - return getattr(multiprocessing, name) - - def get_context(): global _context if _context is None: - if six.PY2: - _context = MpContext() - else: - _context = multiprocessing.get_context("spawn") + _context = multiprocessing.get_context("spawn") return _context diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/process.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/process.py index 8a2b894cb74..d3ff380ad08 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/process.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/process.py @@ -1,11 +1,8 @@ -import sys - import six def cast_env(env): - """Encode all the environment values as the appropriate type for each Python version + """Encode all the environment values as the appropriate type. This assumes that all the data is or can be represented as UTF8""" - env_type = six.ensure_binary if sys.version_info[0] < 3 else six.ensure_str - return {env_type(key): env_type(value) for key, value in six.iteritems(env)} + return {six.ensure_str(key): six.ensure_str(value) for key, value in env.items()} diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/products.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/products.py index abd84094bb3..7ba30537907 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/products.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/products.py @@ -1,6 +1,5 @@ import importlib import imp -from six import iteritems from .browsers import product_list @@ -45,7 +44,7 @@ class Product(object): self.get_timeout_multiplier = getattr(module, data["timeout_multiplier"]) self.executor_classes = {} - for test_type, cls_name in iteritems(data["executor"]): + for test_type, cls_name in data["executor"].items(): cls = getattr(module, cls_name) self.executor_classes[test_type] = cls diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/stability.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/stability.py index 88d1c232bfb..2265523ecc8 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/stability.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/stability.py @@ -5,7 +5,6 @@ import io import os from collections import OrderedDict, defaultdict from datetime import datetime -from six import iteritems from mozlog import reader from mozlog.formatters import JSONFormatter @@ -142,10 +141,10 @@ def process_results(log, iterations): handler = LogHandler() reader.handle_log(reader.read(log), handler) results = handler.results - for test_name, test in iteritems(results): + for test_name, test in results.items(): if is_inconsistent(test["status"], iterations): inconsistent.append((test_name, None, test["status"], [])) - for subtest_name, subtest in iteritems(test["subtests"]): + for subtest_name, subtest in test["subtests"].items(): if is_inconsistent(subtest["status"], iterations): inconsistent.append((test_name, subtest_name, subtest["status"], subtest["messages"])) @@ -235,7 +234,7 @@ def write_results(log, results, iterations, pr_number=None, use_details=False): "tests" if len(results) > 1 else "test")) - for test_name, test in iteritems(results): + for test_name, test in results.items(): baseurl = "http://w3c-test.org/submissions" if "https" in os.path.splitext(test_name)[0].split(".")[1:]: baseurl = "https://w3c-test.org/submissions" @@ -305,7 +304,7 @@ def get_steps(logger, repeat_loop, repeat_restart, kwargs_extras): for kwargs_extra in kwargs_extras: if kwargs_extra: flags_string = " with flags %s" % " ".join( - "%s=%s" % item for item in iteritems(kwargs_extra)) + "%s=%s" % item for item in kwargs_extra.items()) else: flags_string = "" diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testloader.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testloader.py index e57619b45fe..346fe2a2720 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testloader.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testloader.py @@ -1,12 +1,11 @@ import hashlib import json import os -from six.moves.urllib.parse import urlsplit +from urllib.parse import urlsplit from abc import ABCMeta, abstractmethod -from six.moves.queue import Empty +from queue import Empty from collections import defaultdict, deque -from six import ensure_binary, iteritems -from six.moves import range +from six import ensure_binary from . import manifestinclude from . import manifestexpected @@ -41,7 +40,7 @@ class TestGroupsFile(object): raise self.group_by_test = {} - for group, test_ids in iteritems(self._data): + for group, test_ids in self._data.items(): for test_id in test_ids: self.group_by_test[test_id] = group @@ -51,6 +50,16 @@ class TestGroupsFile(object): def __getitem__(self, key): return self._data[key] +def read_include_from_file(file): + new_include = [] + with open(file) as f: + for line in f: + line = line.strip() + # Allow whole-line comments; + # fragments mean we can't have partial line #-based comments + if len(line) > 0 and not line.startswith("#"): + new_include.append(line) + return new_include def update_include_for_groups(test_groups, include): if include is None: @@ -173,7 +182,7 @@ class ManifestLoader(object): def load(self): rv = {} - for url_base, paths in iteritems(self.test_paths): + for url_base, paths in self.test_paths.items(): manifest_file = self.load_manifest(url_base=url_base, **paths) path_data = {"url_base": url_base} @@ -472,7 +481,7 @@ class GroupFileTestSource(TestSource): mp = mpcontext.get_context() test_queue = mp.Queue() - for group_name, test_ids in iteritems(tests_by_group): + for group_name, test_ids in tests_by_group.items(): group_metadata = {"scope": group_name} group = deque() diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testrunner.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testrunner.py index 0c658a158a3..be8acec1c61 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testrunner.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testrunner.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import threading import traceback -from six.moves.queue import Empty +from queue import Empty from collections import namedtuple from mozlog import structuredlog, capture @@ -776,14 +776,19 @@ class TestRunnerManager(threading.Thread): # This might leak a file handle from the queue self.logger.warning("Forcibly terminating runner process") self.test_runner_proc.terminate() + self.logger.debug("After terminating runner process") # Multiprocessing queues are backed by operating system pipes. If # the pipe in the child process had buffered data at the time of # forced termination, the queue is no longer in a usable state # (subsequent attempts to retrieve items may block indefinitely). # Discard the potentially-corrupted queue and create a new one. + self.logger.debug("Recreating command queue") + self.command_queue.cancel_join_thread() self.command_queue.close() self.command_queue = mp.Queue() + self.logger.debug("Recreating remote queue") + self.remote_queue.cancel_join_thread() self.remote_queue.close() self.remote_queue = mp.Queue() else: diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_formatters.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_formatters.py index 60b1b0a5a87..9aa5502bb6b 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_formatters.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_formatters.py @@ -1,6 +1,6 @@ import json import time -from six.moves import cStringIO as StringIO +from io import StringIO import mock diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_testloader.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_testloader.py index 394d2a00177..f68bb5fb33a 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_testloader.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/tests/test_testloader.py @@ -8,6 +8,7 @@ import pytest from mozlog import structured from ..testloader import TestFilter as Filter, TestLoader as Loader +from ..testloader import read_include_from_file from .test_wpttest import make_mock_manifest here = os.path.dirname(__file__) @@ -58,6 +59,28 @@ def test_loader_h2_tests(): assert len(loader.disabled_tests["testharness"]) == 1 assert loader.disabled_tests["testharness"][0].url == "/a/bar.h2.html" +@pytest.mark.xfail(sys.platform == "win32", + reason="NamedTemporaryFile cannot be reopened on Win32") +def test_include_file(): + test_cases = """ +# This is a comment +/foo/bar-error.https.html +/foo/bar-success.https.html +/foo/idlharness.https.any.html +/foo/idlharness.https.any.worker.html + """ + + with tempfile.NamedTemporaryFile(mode="wt") as f: + f.write(test_cases) + f.flush() + + include = read_include_from_file(f.name) + + assert len(include) == 4 + assert "/foo/bar-error.https.html" in include + assert "/foo/bar-success.https.html" in include + assert "/foo/idlharness.https.any.html" in include + assert "/foo/idlharness.https.any.worker.html" in include @pytest.mark.xfail(sys.platform == "win32", reason="NamedTemporaryFile cannot be reopened on Win32") diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/state.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/state.py index 6b6ff1a7e08..a526c24c959 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/state.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/state.py @@ -1,5 +1,5 @@ import os -from six.moves import cPickle as pickle # noqa: N813 +import pickle here = os.path.abspath(os.path.dirname(__file__)) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/tree.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/tree.py index 4127b0ef7dc..2f43bacd15b 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/tree.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/tree.py @@ -3,8 +3,6 @@ import re import subprocess import tempfile -from six.moves import range - from .. import vcs from ..vcs import git, hg @@ -403,5 +401,5 @@ class Commit(object): self.git = self.tree.git def _get_meta(self): - author, email, message = self.git("show", "-s", "--format=format:%an\n%ae\n%B", self.sha1).split(b"\n", 2) - return author.decode('utf-8'), email.decode('utf-8'), self.msg_cls(message.decode('utf-8')) + author, email, message = self.git("show", "-s", "--format=format:%an\n%ae\n%B", self.sha1).decode('utf-8').split("\n", 2) + return author, email, self.msg_cls(message) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/update.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/update.py index 80e509dd383..265a3311beb 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/update.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/update/update.py @@ -1,8 +1,6 @@ import os import sys -from six import itervalues - from .metadata import MetadataUpdateRunner from .sync import SyncFromUpstreamRunner from .tree import GitTree, HgTree, NoVCSTree @@ -111,7 +109,7 @@ class RemoveObsolete(Step): state.tests_path = state.paths["/"]["tests_path"] state.metadata_path = state.paths["/"]["metadata_path"] - for url_paths in itervalues(paths): + for url_paths in paths.values(): tests_path = url_paths["tests_path"] metadata_path = url_paths["metadata_path"] for dirpath, dirnames, filenames in os.walk(metadata_path): diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptcommandline.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptcommandline.py index 2b6d4b357c9..29d6fe5070f 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptcommandline.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptcommandline.py @@ -5,7 +5,7 @@ import sys from collections import OrderedDict from distutils.spawn import find_executable from datetime import timedelta -from six import ensure_text, iterkeys, itervalues, iteritems +from six import ensure_text from . import config from . import wpttest @@ -16,7 +16,7 @@ def abs_path(path): def url_or_path(path): - from six.moves.urllib.parse import urlparse + from urllib.parse import urlparse parsed = urlparse(path) if len(parsed.scheme) > 2: @@ -131,6 +131,8 @@ scheme host and port.""") help="Test types to run") test_selection_group.add_argument("--include", action="append", help="URL prefix to include") + test_selection_group.add_argument("--include-file", action="store", + help="A file listing URL prefix for tests") test_selection_group.add_argument("--exclude", action="append", help="URL prefix to exclude") test_selection_group.add_argument("--include-manifest", type=abs_path, @@ -367,6 +369,10 @@ scheme host and port.""") webkit_group.add_argument("--webkit-port", dest="webkit_port", help="WebKit port") + safari_group = parser.add_argument_group("Safari-specific") + safari_group.add_argument("--kill-safari", dest="kill_safari", action="store_true", default=False, + help="Kill Safari when stopping the browser") + parser.add_argument("test_list", nargs="*", help="List of URLs for tests to run, or paths including tests to run. " "(equivalent to --include)") @@ -408,7 +414,7 @@ def set_from_config(kwargs): ("host_cert_path", "host_cert_path", True), ("host_key_path", "host_key_path", True)]} - for section, values in iteritems(keys): + for section, values in keys.items(): for config_value, kw_value, is_path in values: if kw_value in kwargs and kwargs[kw_value] is None: if not is_path: @@ -444,7 +450,7 @@ def get_test_paths(config): # Set up test_paths test_paths = OrderedDict() - for section in iterkeys(config): + for section in config.keys(): if section.startswith("manifest:"): manifest_opts = config.get(section) url_base = manifest_opts.get("url_base", "/") @@ -470,7 +476,7 @@ def exe_path(name): def check_paths(kwargs): - for test_paths in itervalues(kwargs["test_paths"]): + for test_paths in kwargs["test_paths"].values(): if not ("tests_path" in test_paths and "metadata_path" in test_paths): print("Fatal: must specify both a test path and metadata path") @@ -478,7 +484,7 @@ def check_paths(kwargs): if "manifest_path" not in test_paths: test_paths["manifest_path"] = os.path.join(test_paths["metadata_path"], "MANIFEST.json") - for key, path in iteritems(test_paths): + for key, path in test_paths.items(): name = key.split("_", 1)[0] if name == "manifest": diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/base.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/base.py index c539d4367e9..3069e4cfe8d 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/base.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/base.py @@ -1,5 +1,4 @@ import abc -from six import iteritems, iterkeys, itervalues from ..node import NodeVisitor from ..parser import parse @@ -187,21 +186,21 @@ class ManifestItem(object): def _flatten(self): rv = {} for node in [self, self.root]: - for name, value in iteritems(node._data): + for name, value in node._data.items(): if name not in rv: rv[name] = value return rv def iteritems(self): - for item in iteritems(self._flatten()): + for item in self._flatten().items(): yield item def iterkeys(self): - for item in iterkeys(self._flatten()): + for item in self._flatten().keys(): yield item def itervalues(self): - for item in itervalues(self._flatten()): + for item in self._flatten().values(): yield item def append(self, child): diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py index 3b11e83012a..30dd1449156 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/backends/conditional.py @@ -1,5 +1,5 @@ import operator -from six import ensure_text, iteritems, iterkeys, text_type +from six import ensure_text from ..node import NodeVisitor, DataNode, ConditionalNode, KeyValueNode, ListNode, ValueNode, BinaryExpressionNode, VariableNode from ..parser import parse @@ -300,9 +300,9 @@ class ManifestItem(object): if isinstance(value, list): value_node = ListNode() for item in value: - value_node.append(ValueNode(text_type(item))) + value_node.append(ValueNode(str(item))) else: - value_node = ValueNode(text_type(value)) + value_node = ValueNode(str(value)) if condition is not None: if not isinstance(condition, ConditionalNode): conditional_node = ConditionalNode() @@ -368,17 +368,17 @@ class ManifestItem(object): def _flatten(self): rv = {} for node in [self, self.root]: - for name, value in iteritems(node._data): + for name, value in node._data.items(): if name not in rv: rv[name] = value return rv def iteritems(self): - for item in iteritems(self._flatten()): + for item in self._flatten().items(): yield item def iterkeys(self): - for item in iterkeys(self._flatten()): + for item in self._flatten().keys(): yield item def iter_properties(self): diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/node.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/node.py index 0f82d7006b6..5e9d2b64274 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/node.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/node.py @@ -1,5 +1,3 @@ -from six.moves import range - class NodeVisitor(object): def visit(self, node): # This is ugly as hell, but we don't have multimethods and diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/parser.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/parser.py index 911efac80f2..f6ae1e2940e 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/parser.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptmanifest/parser.py @@ -14,8 +14,7 @@ from __future__ import unicode_literals -from six import binary_type, text_type, BytesIO, unichr -from six.moves import range +from io import BytesIO from .node import (Node, AtomNode, BinaryExpressionNode, BinaryOperatorNode, ConditionalNode, DataNode, IndexNode, KeyValueNode, ListNode, @@ -50,7 +49,7 @@ atoms = {"True": True, "Reset": object()} def decode(s): - assert isinstance(s, text_type) + assert isinstance(s, str) return s @@ -79,7 +78,7 @@ class Tokenizer(object): def tokenize(self, stream): self.reset() - assert not isinstance(stream, text_type) + assert not isinstance(stream, str) if isinstance(stream, bytes): stream = BytesIO(stream) if not hasattr(stream, "name"): @@ -89,7 +88,7 @@ class Tokenizer(object): self.next_line_state = self.line_start_state for i, line in enumerate(stream): - assert isinstance(line, binary_type) + assert isinstance(line, bytes) self.state = self.next_line_state assert self.state is not None states = [] @@ -97,7 +96,7 @@ class Tokenizer(object): self.line_number = i + 1 self.index = 0 self.line = line.decode('utf-8').rstrip() - assert isinstance(self.line, text_type) + assert isinstance(self.line, str) while self.state != self.eol_state: states.append(self.state) tokens = self.state() @@ -505,7 +504,7 @@ class Tokenizer(object): value += self.escape_value(c) self.consume() - return unichr(value) + return chr(value) def escape_value(self, c): if '0' <= c <= '9': diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptrunner.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptrunner.py index 7182510a29d..0294e8720ac 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptrunner.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wptrunner.py @@ -4,8 +4,6 @@ import json import os import sys -from six import iteritems, itervalues - import wptserve from wptserve import sslutils @@ -67,6 +65,8 @@ def get_loader(test_paths, product, debug=None, run_info_extras=None, chunker_kw manifest_filters = [] include = kwargs["include"] + if kwargs["include_file"]: + include.extend(testloader.read_include_from_file(kwargs["include_file"])) if test_groups: include = testloader.update_include_for_groups(test_groups, include) @@ -117,7 +117,7 @@ def list_disabled(test_paths, product, **kwargs): run_info, test_loader = get_loader(test_paths, product, run_info_extras=run_info_extras, **kwargs) - for test_type, tests in iteritems(test_loader.disabled_tests): + for test_type, tests in test_loader.disabled_tests.items(): for test in tests: rv.append({"test": test.id, "reason": test.disabled()}) print(json.dumps(rv, indent=2)) @@ -144,7 +144,7 @@ def get_pause_after_test(test_loader, **kwargs): if kwargs["debug_test"]: return True tests = test_loader.tests - is_single_testharness = (sum(len(item) for item in itervalues(tests)) == 1 and + is_single_testharness = (sum(len(item) for item in tests.values()) == 1 and len(tests.get("testharness", [])) == 1) if kwargs["repeat"] == 1 and kwargs["rerun"] == 1 and is_single_testharness: return True diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wpttest.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wpttest.py index ed9184ef5ec..b7a7cec1977 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wpttest.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/wpttest.py @@ -1,9 +1,8 @@ import os import subprocess import sys -from six.moves.urllib.parse import urljoin from collections import defaultdict -from six import iteritems, string_types +from urllib.parse import urljoin from .wptmanifest.parser import atoms @@ -307,7 +306,7 @@ class Test(object): rv = {} for meta in self.itermeta(None): threshold = meta.leak_threshold - for key, value in iteritems(threshold): + for key, value in threshold.items(): if key not in rv: rv[key] = value return rv @@ -349,7 +348,7 @@ class Test(object): try: expected = metadata.get("expected") - if isinstance(expected, string_types): + if isinstance(expected, str): return expected elif isinstance(expected, list): return expected[0] diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/base.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/base.py index 7ce53a590b6..2e7fa443c36 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/base.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/base.py @@ -6,10 +6,9 @@ import os import pytest import unittest -from six.moves.urllib.parse import urlencode, urlunsplit -from six.moves.urllib.request import Request as BaseRequest -from six.moves.urllib.request import urlopen -from six import binary_type, iteritems, PY3 +from urllib.parse import urlencode, urlunsplit +from urllib.request import Request as BaseRequest +from urllib.request import urlopen from hyper import HTTP20Connection, tls import ssl @@ -37,7 +36,7 @@ class Request(BaseRequest): if hasattr(data, "items"): data = urlencode(data).encode("ascii") - assert isinstance(data, binary_type) + assert isinstance(data, bytes) if hasattr(BaseRequest, "add_data"): BaseRequest.add_data(self, data) @@ -68,7 +67,7 @@ class TestUsingServer(unittest.TestCase): if headers is None: headers = {} - for name, value in iteritems(headers): + for name, value in headers.items(): req.add_header(name, value) if body is not None: @@ -80,10 +79,7 @@ class TestUsingServer(unittest.TestCase): return urlopen(req) def assert_multiple_headers(self, resp, name, values): - if PY3: - assert resp.info().get_all(name) == values - else: - assert resp.info()[name] == ", ".join(values) + assert resp.info().get_all(name) == values @pytest.mark.skipif(not wptserve.utils.http2_compatible(), reason="h2 server only works in python 2.7.10+ and Python 3.6+") diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_handlers.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_handlers.py index 261bf3be2c2..0acfac4d194 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_handlers.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_handlers.py @@ -5,7 +5,7 @@ import unittest import uuid import pytest -from six.moves.urllib.error import HTTPError +from urllib.error import HTTPError wptserve = pytest.importorskip("wptserve") from .base import TestUsingServer, TestUsingH2Server, doc_root diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_input_file.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_input_file.py index 98176e58386..93db62c8422 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_input_file.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_input_file.py @@ -1,8 +1,6 @@ -import sys from io import BytesIO import pytest -from six import PY2 from wptserve.request import InputFile @@ -121,8 +119,6 @@ def test_readlines(): assert input_file.readlines() == test_file.readlines() -@pytest.mark.xfail(sys.platform == "win32" and PY2, - reason="https://github.com/web-platform-tests/wpt/issues/12949") def test_readlines_file_bigger_than_buffer(): old_max_buf = InputFile.max_buffer_size InputFile.max_buffer_size = 10 @@ -140,8 +136,6 @@ def test_iter(): assert a == b -@pytest.mark.xfail(sys.platform == "win32" and PY2, - reason="https://github.com/web-platform-tests/wpt/issues/12949") def test_iter_file_bigger_than_buffer(): old_max_buf = InputFile.max_buffer_size InputFile.max_buffer_size = 10 diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_pipes.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_pipes.py index a00de71d796..8b40e1c84ff 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_pipes.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_pipes.py @@ -2,9 +2,7 @@ import os import unittest import time import json - -from six import assertRegex -from six.moves import urllib +import urllib import pytest @@ -121,7 +119,7 @@ server: http://localhost:{0}""".format(self.server.port).encode("ascii") def test_sub_uuid(self): resp = self.request("/sub_uuid.sub.txt") - assertRegex(self, resp.read().rstrip(), b"Before [a-f0-9-]+ After") + self.assertRegex(resp.read().rstrip(), b"Before [a-f0-9-]+ After") def test_sub_var(self): resp = self.request("/sub_var.sub.txt") diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_request.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_request.py index b30e6c4cb64..9371cd0e249 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_request.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_request.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import pytest -from six import PY3 + +from urllib.parse import quote_from_bytes wptserve = pytest.importorskip("wptserve") from .base import TestUsingServer @@ -144,12 +145,7 @@ class TestRequest(TestUsingServer): # We intentionally choose an encoding that's not the default UTF-8. encoded_text = u"どうも".encode("shift-jis") - if PY3: - from urllib.parse import quote_from_bytes - quoted = quote_from_bytes(encoded_text) - else: - from urllib import quote - quoted = quote(encoded_text) + quoted = quote_from_bytes(encoded_text) resp = self.request(route[1], query="foo="+quoted) self.assertEqual(encoded_text, resp.read()) @@ -163,13 +159,8 @@ class TestRequest(TestUsingServer): # We intentionally choose an encoding that's not the default UTF-8. encoded_text = u"どうも".encode("shift-jis") - if PY3: - from urllib.parse import quote_from_bytes - # After urlencoding, the string should only contain ASCII. - quoted = quote_from_bytes(encoded_text).encode("ascii") - else: - from urllib import quote - quoted = quote(encoded_text) + # After urlencoding, the string should only contain ASCII. + quoted = quote_from_bytes(encoded_text).encode("ascii") resp = self.request(route[1], method="POST", body=b"foo="+quoted) self.assertEqual(encoded_text, resp.read()) diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_response.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_response.py index cc740bb99df..23f13f86d11 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_response.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_response.py @@ -1,11 +1,12 @@ import os import unittest import json +import types + +from http.client import BadStatusLine from io import BytesIO import pytest -from six import create_bound_method, PY3 -from six.moves.http_client import BadStatusLine wptserve = pytest.importorskip("wptserve") from .base import TestUsingServer, TestUsingH2Server, doc_root @@ -22,8 +23,8 @@ class TestResponse(TestUsingServer): def test_head_without_body(self): @wptserve.handlers.handler def handler(request, response): - response.writer.end_headers = create_bound_method(send_body_as_header, - response.writer) + response.writer.end_headers = types.MethodType(send_body_as_header, + response.writer) return [("X-Test", "TEST")], "body\r\n" route = ("GET", "/test/test_head_without_body", handler) @@ -37,8 +38,8 @@ class TestResponse(TestUsingServer): @wptserve.handlers.handler def handler(request, response): response.send_body_for_head_request = True - response.writer.end_headers = create_bound_method(send_body_as_header, - response.writer) + response.writer.end_headers = types.MethodType(send_body_as_header, + response.writer) return [("X-Test", "TEST")], "body\r\n" route = ("GET", "/test/test_head_with_body", handler) @@ -177,13 +178,9 @@ class TestResponse(TestUsingServer): route = ("GET", "/test/test_write_raw_content", handler) self.server.router.register(*route) - try: - resp = self.request(route[1]) - assert resp.read() == resp_content - except BadStatusLine as e: - # In Python3, an invalid HTTP request should throw BadStatusLine. - assert PY3 - assert str(e) == resp_content.decode('utf-8') + with pytest.raises(BadStatusLine) as e: + self.request(route[1]) + assert str(e.value) == resp_content.decode('utf-8') class TestH2Response(TestUsingH2Server): def test_write_without_ending_stream(self): diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_server.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_server.py index 051dc3f1def..f3b095cfbf0 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_server.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/functional/test_server.py @@ -1,7 +1,7 @@ import unittest import pytest -from six.moves.urllib.error import HTTPError +from urllib.error import HTTPError wptserve = pytest.importorskip("wptserve") from .base import TestUsingServer, TestUsingH2Server diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/test_request.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/test_request.py index 91390b28e41..f69470d4b6d 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/test_request.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/test_request.py @@ -1,5 +1,4 @@ import mock -from six import binary_type from wptserve.request import Request, RequestHeaders, MultiDict @@ -49,9 +48,9 @@ def test_request_headers_encoding(): 'x-bar': ['bar1', 'bar2'], }) headers = RequestHeaders(raw_headers) - assert isinstance(headers['x-foo'], binary_type) - assert isinstance(headers['x-bar'], binary_type) - assert isinstance(headers.get_list('x-bar')[0], binary_type) + assert isinstance(headers['x-foo'], bytes) + assert isinstance(headers['x-bar'], bytes) + assert isinstance(headers.get_list('x-bar')[0], bytes) def test_request_url_from_server_address(): diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/test_response.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/test_response.py index f8048ac1ce5..16440da2b27 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/test_response.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/test_response.py @@ -1,5 +1,5 @@ import mock -from six import BytesIO +from io import BytesIO from wptserve.response import Response diff --git a/tests/wpt/web-platform-tests/tools/wptserve/tests/test_stash.py b/tests/wpt/web-platform-tests/tools/wptserve/tests/test_stash.py index 8ca3944cdb0..4157db5726d 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/tests/test_stash.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/tests/test_stash.py @@ -5,7 +5,6 @@ import sys from multiprocessing.managers import BaseManager import pytest -from six import PY3 Stash = pytest.importorskip("wptserve.stash").Stash @@ -67,7 +66,7 @@ class SlowLock(BaseManager): @pytest.mark.xfail(sys.platform == "win32" or - PY3 and multiprocessing.get_start_method() == "spawn", + multiprocessing.get_start_method() == "spawn", reason="https://github.com/web-platform-tests/wpt/issues/16938") def test_delayed_lock(add_cleanup): """Ensure that delays in proxied Lock retrieval do not interfere with @@ -110,7 +109,7 @@ class SlowDict(BaseManager): @pytest.mark.xfail(sys.platform == "win32" or - PY3 and multiprocessing.get_start_method() == "spawn", + multiprocessing.get_start_method() == "spawn", reason="https://github.com/web-platform-tests/wpt/issues/16938") def test_delayed_dict(add_cleanup): """Ensure that delays in proxied `dict` retrieval do not interfere with diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/config.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/config.py index 4d653f522a0..843f6e664e3 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/config.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/config.py @@ -2,9 +2,7 @@ import copy import logging import os from collections import defaultdict - -from six.moves.collections_abc import Mapping -from six import integer_types, iteritems, itervalues, string_types +from collections.abc import Mapping from . import sslutils from .utils import get_port @@ -20,7 +18,7 @@ _renamed_props = { def _merge_dict(base_dict, override_dict): rv = base_dict.copy() - for key, value in iteritems(base_dict): + for key, value in base_dict.items(): if key in override_dict: if isinstance(value, dict): rv[key] = _merge_dict(value, override_dict[key]) @@ -89,7 +87,7 @@ class Config(Mapping): target = target[part] value = target[key[-1]] if isinstance(value, dict): - target[key[-1]] = {k:v for (k,v) in iteritems(value) if not k.startswith("op")} + target[key[-1]] = {k:v for (k,v) in value.items() if not k.startswith("op")} else: target[key[-1]] = [x for x in value if not x.startswith("op")] @@ -98,9 +96,9 @@ class Config(Mapping): def json_types(obj): if isinstance(obj, dict): - return {key: json_types(value) for key, value in iteritems(obj)} - if (isinstance(obj, string_types) or - isinstance(obj, integer_types) or + return {key: json_types(value) for key, value in obj.items()} + if (isinstance(obj, str) or + isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, bool) or obj is None): @@ -203,13 +201,13 @@ class ConfigBuilder(object): self.log_level = level_name self._logger_name = logger.name - for k, v in iteritems(self._default): + for k, v in self._default.items(): self._data[k] = kwargs.pop(k, v) self._data["subdomains"] = subdomains self._data["not_subdomains"] = not_subdomains - for k, new_k in iteritems(_renamed_props): + for k, new_k in _renamed_props.items(): if k in kwargs: self.logger.warning( "%s in config is deprecated; use %s instead" % ( @@ -242,7 +240,7 @@ class ConfigBuilder(object): if k in override: self._set_override(k, override.pop(k)) - for k, new_k in iteritems(_renamed_props): + for k, new_k in _renamed_props.items(): if k in override: self.logger.warning( "%s in config is deprecated; use %s instead" % ( @@ -287,7 +285,7 @@ class ConfigBuilder(object): def _get_ports(self, data): new_ports = defaultdict(list) - for scheme, ports in iteritems(data["ports"]): + for scheme, ports in data["ports"].items(): if scheme in ["wss", "https"] and not sslutils.get_cls(data["ssl"]["type"]).ssl_enabled: continue for i, port in enumerate(ports): @@ -301,7 +299,7 @@ class ConfigBuilder(object): hosts[""] = data["browser_host"] rv = {} - for name, host in iteritems(hosts): + for name, host in hosts.items(): rv[name] = {subdomain: (subdomain.encode("idna").decode("ascii") + u"." + host) for subdomain in data["subdomains"]} rv[name][""] = host @@ -313,7 +311,7 @@ class ConfigBuilder(object): hosts[""] = data["browser_host"] rv = {} - for name, host in iteritems(hosts): + for name, host in hosts.items(): rv[name] = {subdomain: (subdomain.encode("idna").decode("ascii") + u"." + host) for subdomain in data["not_subdomains"]} return rv @@ -327,13 +325,13 @@ class ConfigBuilder(object): def _get_domains_set(self, data): return {domain - for per_host_domains in itervalues(data["domains"]) - for domain in itervalues(per_host_domains)} + for per_host_domains in data["domains"].values() + for domain in per_host_domains.values()} def _get_not_domains_set(self, data): return {domain - for per_host_domains in itervalues(data["not_domains"]) - for domain in itervalues(per_host_domains)} + for per_host_domains in data["not_domains"].values() + for domain in per_host_domains.values()} def _get_all_domains_set(self, data): return data["domains_set"] | data["not_domains_set"] diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/handlers.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/handlers.py index 0fc11c190d1..9eb4a44c199 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/handlers.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/handlers.py @@ -3,8 +3,7 @@ import os import traceback from collections import defaultdict -from six.moves.urllib.parse import quote, unquote, urljoin -from six import iteritems +from urllib.parse import quote, unquote, urljoin from .constants import content_types from .pipes import Pipeline, template @@ -482,7 +481,7 @@ class StringHandler(object): self.data = data self.resp_headers = [("Content-Type", content_type)] - for k, v in iteritems(headers): + for k, v in headers.items(): self.resp_headers.append((k.replace("_", "-"), v)) self.handler = handler(self.handle_request) diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/pipes.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/pipes.py index bbf25e6fe11..2aa0aaaee8d 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/pipes.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/pipes.py @@ -7,8 +7,7 @@ import re import time import uuid -from six.moves import StringIO -from six import text_type, binary_type +from io import StringIO try: from html import escape @@ -305,7 +304,7 @@ class ReplacementTokenizer(object): return ("var", token) def tokenize(self, string): - assert isinstance(string, binary_type) + assert isinstance(string, bytes) return self.scanner.scan(string)[0] scanner = re.Scanner([(br"\$\w+:", var), @@ -320,7 +319,7 @@ class FirstWrapper(object): def __getitem__(self, key): try: - if isinstance(key, text_type): + if isinstance(key, str): key = key.encode('iso-8859-1') return self.params.first(key) except KeyError: @@ -408,7 +407,7 @@ class SubFunctions(object): @staticmethod def file_hash(request, algorithm, path): - assert isinstance(algorithm, text_type) + assert isinstance(algorithm, str) if algorithm not in SubFunctions.supported_algorithms: raise ValueError("Unsupported encryption algorithm: '%s'" % algorithm) @@ -460,12 +459,12 @@ def template(request, content, escape_type="html"): tokens = deque(tokens) token_type, field = tokens.popleft() - assert isinstance(field, text_type) + assert isinstance(field, str) if token_type == "var": variable = field token_type, field = tokens.popleft() - assert isinstance(field, text_type) + assert isinstance(field, str) else: variable = None @@ -516,7 +515,7 @@ def template(request, content, escape_type="html"): "unexpected token type %s (token '%r'), expected ident or arguments" % (ttype, field) ) - assert isinstance(value, (int, (binary_type, text_type))), tokens + assert isinstance(value, (int, (bytes, str))), tokens if variable is not None: variables[variable] = value @@ -527,10 +526,10 @@ def template(request, content, escape_type="html"): # Should possibly support escaping for other contexts e.g. script # TODO: read the encoding of the response # cgi.escape() only takes text strings in Python 3. - if isinstance(value, binary_type): + if isinstance(value, bytes): value = value.decode("utf-8") elif isinstance(value, int): - value = text_type(value) + value = str(value) return escape_func(value).encode("utf-8") template_regexp = re.compile(br"{{([^}]*)}}") diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/request.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/request.py index dbfe067ba7f..76644cc4969 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/request.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/request.py @@ -2,9 +2,9 @@ import base64 import cgi import tempfile -from six import BytesIO, binary_type, iteritems, PY3 -from six.moves.http_cookies import BaseCookie -from six.moves.urllib.parse import parse_qsl, urlsplit +from http.cookies import BaseCookie +from io import BytesIO +from urllib.parse import parse_qsl, urlsplit from . import stash from .utils import HTTPException, isomorphic_encode, isomorphic_decode @@ -301,9 +301,8 @@ class Request(object): if self._GET is None: kwargs = { "keep_blank_values": True, + "encoding": "iso-8859-1", } - if PY3: - kwargs["encoding"] = "iso-8859-1" params = parse_qsl(self.url_parts.query, **kwargs) self._GET = MultiDict() for key, value in params: @@ -321,9 +320,8 @@ class Request(object): "environ": {"REQUEST_METHOD": self.method}, "headers": self.raw_headers, "keep_blank_values": True, + "encoding": "iso-8859-1", } - if PY3: - kwargs["encoding"] = "iso-8859-1" fs = cgi.FieldStorage(**kwargs) self._POST = MultiDict.from_field_storage(fs) self.raw_input.seek(pos) @@ -336,7 +334,7 @@ class Request(object): cookie_headers = self.headers.get("cookie", b"") parser.load(cookie_headers) cookies = Cookies() - for key, value in iteritems(parser): + for key, value in parser.items(): cookies[isomorphic_encode(key)] = CookieValue(value) self._cookies = cookies return self._cookies @@ -630,11 +628,9 @@ class BinaryCookieParser(BaseCookie): This overrides and calls BaseCookie.load. Unlike BaseCookie.load, it does not accept dictionaries. """ - assert isinstance(rawdata, binary_type) - if PY3: - # BaseCookie.load expects a native string, which in Python 3 is text. - rawdata = isomorphic_decode(rawdata) - super(BinaryCookieParser, self).load(rawdata) + assert isinstance(rawdata, bytes) + # BaseCookie.load expects a native string + super(BinaryCookieParser, self).load(isomorphic_decode(rawdata)) class Cookies(MultiDict): @@ -675,7 +671,7 @@ class Authentication(object): if "authorization" in headers: header = headers.get("authorization") - assert isinstance(header, binary_type) + assert isinstance(header, bytes) auth_type, data = header.split(b" ", 1) if auth_type in auth_schemes: self.username, self.password = auth_schemes[auth_type](data) @@ -683,6 +679,6 @@ class Authentication(object): raise HTTPException(400, "Unsupported authentication scheme %s" % auth_type) def decode_basic(self, data): - assert isinstance(data, binary_type) + assert isinstance(data, bytes) decoded_data = base64.b64decode(data) return decoded_data.split(b":", 1) diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/response.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/response.py index 6e5ee115a55..8763cca9036 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/response.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/response.py @@ -6,9 +6,8 @@ import socket import uuid from hpack.struct import HeaderTuple +from http.cookies import BaseCookie, Morsel from hyperframe.frame import HeadersFrame, DataFrame, ContinuationFrame -from six import binary_type, text_type, integer_types, itervalues, PY3 -from six.moves.http_cookies import BaseCookie, Morsel from .constants import response_codes, h2_headers from .logger import get_logger @@ -91,7 +90,7 @@ class Response(object): message = value[1] # Only call str() if message is not a string type, so that we # don't get `str(b"foo") == "b'foo'"` in Python 3. - if not isinstance(message, (binary_type, text_type)): + if not isinstance(message, (bytes, str)): message = str(message) self._status = (code, message) else: @@ -122,9 +121,8 @@ class Response(object): max_age = 0 expires = timedelta(days=-1) - if PY3: - name = isomorphic_decode(name) - value = isomorphic_decode(value) + name = isomorphic_decode(name) + value = isomorphic_decode(value) days = {i+1: name for i, name in enumerate(["jan", "feb", "mar", "apr", "may", "jun", @@ -163,15 +161,11 @@ class Response(object): def unset_cookie(self, name): """Remove a cookie from those that are being sent with the response""" - if PY3: - name = isomorphic_decode(name) + name = isomorphic_decode(name) cookies = self.headers.get("Set-Cookie") parser = BaseCookie() for cookie in cookies: - if PY3: - # BaseCookie.load expects a text string. - cookie = isomorphic_decode(cookie) - parser.load(cookie) + parser.load(isomorphic_decode(cookie)) if name in parser.keys(): del self.headers["Set-Cookie"] @@ -199,9 +193,9 @@ class Response(object): string facilitating non-streaming operations like template substitution. """ - if isinstance(self.content, binary_type): + if isinstance(self.content, bytes): yield self.content - elif isinstance(self.content, text_type): + elif isinstance(self.content, str): yield self.content.encode(self.encoding) elif hasattr(self.content, "read"): if read_file: @@ -256,7 +250,7 @@ class MultipartContent(object): def __init__(self, boundary=None, default_content_type=None): self.items = [] if boundary is None: - boundary = text_type(uuid.uuid4()) + boundary = str(uuid.uuid4()) self.boundary = boundary self.default_content_type = default_content_type @@ -284,7 +278,7 @@ class MultipartContent(object): class MultipartPart(object): def __init__(self, data, content_type=None, headers=None): - assert isinstance(data, binary_type), data + assert isinstance(data, bytes), data self.headers = ResponseHeaders() if content_type is not None: @@ -303,8 +297,8 @@ class MultipartPart(object): def to_bytes(self): rv = [] for key, value in self.headers: - assert isinstance(key, binary_type) - assert isinstance(value, binary_type) + assert isinstance(key, bytes) + assert isinstance(value, bytes) rv.append(b"%s: %s" % (key, value)) rv.append(b"") rv.append(self.data) @@ -313,7 +307,7 @@ class MultipartPart(object): def _maybe_encode(s): """Encode a string or an int into binary data using isomorphic_encode().""" - if isinstance(s, integer_types): + if isinstance(s, int): return b"%i" % (s,) return isomorphic_encode(s) @@ -377,7 +371,7 @@ class ResponseHeaders(object): self.set(key, value) def __iter__(self): - for key, values in itervalues(self.data): + for key, values in self.data.values(): for value in values: yield key, value @@ -447,10 +441,10 @@ class H2ResponseWriter(object): for header, value in headers: # h2_headers are native strings # header field names are strings of ASCII - if isinstance(header, binary_type): + if isinstance(header, bytes): header = header.decode('ascii') # value in headers can be either string or integer - if isinstance(value, binary_type): + if isinstance(value, bytes): value = self.decode(value) if header in h2_headers: header = ':' + header @@ -482,7 +476,7 @@ class H2ResponseWriter(object): :param last: Flag to signal if this is the last frame in stream. :param stream_id: Id of stream to send frame on. Will use the request stream ID if None """ - if isinstance(item, (text_type, binary_type)): + if isinstance(item, (str, bytes)): data = BytesIO(self.encode(item)) else: data = item @@ -638,18 +632,18 @@ class H2ResponseWriter(object): def decode(self, data): """Convert bytes to unicode according to response.encoding.""" - if isinstance(data, binary_type): + if isinstance(data, bytes): return data.decode(self._response.encoding) - elif isinstance(data, text_type): + elif isinstance(data, str): return data else: raise ValueError(type(data)) def encode(self, data): """Convert unicode to bytes according to response.encoding.""" - if isinstance(data, binary_type): + if isinstance(data, bytes): return data - elif isinstance(data, text_type): + elif isinstance(data, str): return data.encode(self._response.encoding) else: raise ValueError @@ -707,7 +701,7 @@ class ResponseWriter(object): if not self.write(b": "): return False if isinstance(value, int): - if not self.write(text_type(value)): + if not self.write(str(value)): return False elif not self.write(value): return False @@ -720,7 +714,7 @@ class ResponseWriter(object): if not self.write_header(name, f()): return False - if (isinstance(self._response.content, (binary_type, text_type)) and + if (isinstance(self._response.content, (bytes, str)) and not self._seen_header("content-length")): #Would be nice to avoid double-encoding here if not self.write_header("Content-Length", len(self.encode(self._response.content))): @@ -767,7 +761,7 @@ class ResponseWriter(object): """Writes the data 'as is'""" if data is None: raise ValueError('data cannot be None') - if isinstance(data, (text_type, binary_type)): + if isinstance(data, (str, bytes)): # Deliberately allows both text and binary types. See `self.encode`. return self.write(data) else: @@ -805,9 +799,9 @@ class ResponseWriter(object): def encode(self, data): """Convert unicode to bytes according to response.encoding.""" - if isinstance(data, binary_type): + if isinstance(data, bytes): return data - elif isinstance(data, text_type): + elif isinstance(data, str): return data.encode(self._response.encoding) else: raise ValueError("data %r should be text or binary, but is %s" % (data, type(data))) diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/router.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/router.py index d1704a7bbd5..5a91de30575 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/router.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/router.py @@ -3,7 +3,6 @@ import re import sys from .logger import get_logger -from six import binary_type, text_type any_method = object() @@ -146,7 +145,7 @@ class Router(object): object and the response object. """ - if isinstance(methods, (binary_type, text_type)) or methods is any_method: + if isinstance(methods, (bytes, str)) or methods is any_method: methods = [methods] for method in methods: self.routes.append((method, compile_path_match(path), handler)) diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/server.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/server.py index 2b5ed4a27d5..1a007cda5b4 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/server.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/server.py @@ -1,18 +1,16 @@ -from six.moves import BaseHTTPServer import errno +import http.server import os import socket -from six.moves.socketserver import ThreadingMixIn +from socketserver import ThreadingMixIn import ssl import sys import threading import time import traceback -from six import binary_type, text_type import uuid from collections import OrderedDict - -from six.moves.queue import Queue +from queue import Queue from h2.config import H2Configuration from h2.connection import H2Connection @@ -21,7 +19,7 @@ from h2.exceptions import StreamClosedError, ProtocolError from h2.settings import SettingCodes from h2.utilities import extract_method_header -from six.moves.urllib.parse import urlsplit, urlunsplit +from urllib.parse import urlsplit, urlunsplit from mod_pywebsocket import dispatch from mod_pywebsocket.handshake import HandshakeException @@ -38,13 +36,12 @@ from .ws_h2_handshake import WsH2Handshaker # We need to stress test that browsers can send/receive many headers (there is # no specified limit), but the Python stdlib has an arbitrary limit of 100 -# headers. Hitting the limit would produce an exception that is silently caught -# in Python 2 but leads to HTTP 431 in Python 3, so we monkey patch it higher. +# headers. Hitting the limit leads to HTTP 431, so we monkey patch it higher. # https://bugs.python.org/issue26586 # https://github.com/web-platform-tests/wpt/pull/24451 -from six.moves import http_client -assert isinstance(getattr(http_client, '_MAXHEADERS'), int) -setattr(http_client, '_MAXHEADERS', 512) +import http.client +assert isinstance(getattr(http.client, '_MAXHEADERS'), int) +setattr(http.client, '_MAXHEADERS', 512) """ HTTP server designed for testing purposes. @@ -106,7 +103,7 @@ class RequestRewriter(object): :param output_path: Path to replace the input path with in the request. """ - if isinstance(methods, (binary_type, text_type)): + if isinstance(methods, (bytes, str)): methods = [methods] self.rules[input_path] = (methods, output_path) @@ -129,7 +126,7 @@ class RequestRewriter(object): request_handler.path = new_url -class WebTestServer(ThreadingMixIn, BaseHTTPServer.HTTPServer): +class WebTestServer(ThreadingMixIn, http.server.HTTPServer): allow_reuse_address = True acceptable_errors = (errno.EPIPE, errno.ECONNABORTED) request_queue_size = 2000 @@ -190,8 +187,7 @@ class WebTestServer(ThreadingMixIn, BaseHTTPServer.HTTPServer): else: hostname_port = ("",server_address[1]) - #super doesn't work here because BaseHTTPServer.HTTPServer is old-style - BaseHTTPServer.HTTPServer.__init__(self, hostname_port, request_handler_cls, **kwargs) + http.server.HTTPServer.__init__(self, hostname_port, request_handler_cls, **kwargs) if config is not None: Server.config = config @@ -236,12 +232,12 @@ class WebTestServer(ThreadingMixIn, BaseHTTPServer.HTTPServer): self.logger.error(traceback.format_exc()) -class BaseWebTestRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): +class BaseWebTestRequestHandler(http.server.BaseHTTPRequestHandler): """RequestHandler for WebTestHttpd""" def __init__(self, *args, **kwargs): self.logger = get_logger() - BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs) + http.server.BaseHTTPRequestHandler.__init__(self, *args, **kwargs) def finish_handling_h1(self, request_line_is_valid): diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/sslutils/openssl.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/sslutils/openssl.py index 64f6d5fb2db..040fc3ac55b 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/sslutils/openssl.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/sslutils/openssl.py @@ -6,8 +6,6 @@ import subprocess import tempfile from datetime import datetime, timedelta -from six import iteritems, PY2 - # Amount of time beyond the present to consider certificates "expired." This # allows certificates to be proactively re-generated in the "buffer" period # prior to their exact expiration time. @@ -18,11 +16,7 @@ def _ensure_str(s, encoding): """makes sure s is an instance of str, converting with encoding if needed""" if isinstance(s, str): return s - - if PY2: - return s.encode(encoding) - else: - return s.decode(encoding) + return s.decode(encoding) class OpenSSL(object): @@ -79,7 +73,7 @@ class OpenSSL(object): # Copy the environment, converting to plain strings. Win32 StartProcess # is picky about all the keys/values being str (on both Py2/3). env = {} - for k, v in iteritems(os.environ): + for k, v in os.environ.items(): env[_ensure_str(k, "utf8")] = _ensure_str(v, "utf8") if self.base_conf_path is not None: diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/stash.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/stash.py index bf6e59927be..66c27131913 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/stash.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/stash.py @@ -1,12 +1,10 @@ import base64 import json import os -import six import threading import uuid from multiprocessing.managers import AcquirerProxy, BaseManager, DictProxy -from six import text_type, binary_type from .utils import isomorphic_encode @@ -67,10 +65,10 @@ def store_env_config(address, authkey): def start_server(address=None, authkey=None, mp_context=None): - if isinstance(authkey, text_type): + if isinstance(authkey, str): authkey = authkey.encode("ascii") kwargs = {} - if six.PY3 and mp_context is not None: + if mp_context is not None: kwargs["ctx"] = mp_context manager = ServerDictManager(address, authkey, **kwargs) manager.start() @@ -160,7 +158,7 @@ class Stash(object): # This key format is required to support using the path. Since the data # passed into the stash can be a DictProxy which wouldn't detect # changes when writing to a subdict. - if isinstance(key, binary_type): + if isinstance(key, bytes): # UUIDs are within the ASCII charset. key = key.decode('ascii') return (isomorphic_encode(path), uuid.UUID(key).bytes) diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/utils.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/utils.py index 1ea2d8c902f..e8b607bd5e0 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/utils.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/utils.py @@ -1,24 +1,22 @@ import socket import sys -from six import binary_type, text_type - def isomorphic_decode(s): """Decodes a binary string into a text string using iso-8859-1. - Returns `unicode` in Python 2 and `str` in Python 3. The function is a - no-op if the argument already has a text type. iso-8859-1 is chosen because - it is an 8-bit encoding whose code points range from 0x0 to 0xFF and the - values are the same as the binary representations, so any binary string can - be decoded into and encoded from iso-8859-1 without any errors or data - loss. Python 3 also uses iso-8859-1 (or latin-1) extensively in http: + Returns `str`. The function is a no-op if the argument already has a text + type. iso-8859-1 is chosen because it is an 8-bit encoding whose code + points range from 0x0 to 0xFF and the values are the same as the binary + representations, so any binary string can be decoded into and encoded from + iso-8859-1 without any errors or data loss. Python 3 also uses iso-8859-1 + (or latin-1) extensively in http: https://github.com/python/cpython/blob/273fc220b25933e443c82af6888eb1871d032fb8/Lib/http/client.py#L213 """ - if isinstance(s, text_type): + if isinstance(s, str): return s - if isinstance(s, binary_type): + if isinstance(s, bytes): return s.decode("iso-8859-1") raise TypeError("Unexpected value (expecting string-like): %r" % s) @@ -27,14 +25,13 @@ def isomorphic_decode(s): def isomorphic_encode(s): """Encodes a text-type string into binary data using iso-8859-1. - Returns `str` in Python 2 and `bytes` in Python 3. The function is a no-op - if the argument already has a binary type. This is the counterpart of - isomorphic_decode. + Returns `bytes`. The function is a no-op if the argument already has a + binary type. This is the counterpart of isomorphic_decode. """ - if isinstance(s, binary_type): + if isinstance(s, bytes): return s - if isinstance(s, text_type): + if isinstance(s, str): return s.encode("iso-8859-1") raise TypeError("Unexpected value (expecting string-like): %r" % s) diff --git a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/ws_h2_handshake.py b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/ws_h2_handshake.py index 98796c0baf9..c813ecb5a0b 100644 --- a/tests/wpt/web-platform-tests/tools/wptserve/wptserve/ws_h2_handshake.py +++ b/tests/wpt/web-platform-tests/tools/wptserve/wptserve/ws_h2_handshake.py @@ -11,8 +11,6 @@ from mod_pywebsocket import common from mod_pywebsocket.stream import Stream from mod_pywebsocket.stream import StreamOptions from mod_pywebsocket import util -from six.moves import map -from six.moves import range # TODO: We are using "private" methods of pywebsocket. We might want to # refactor pywebsocket to expose those methods publicly. Also, _get_origin diff --git a/tests/wpt/web-platform-tests/touch-events/multi-touch-interfaces-manual.html b/tests/wpt/web-platform-tests/touch-events/multi-touch-interfaces-manual.html deleted file mode 100644 index e9f1b91c486..00000000000 --- a/tests/wpt/web-platform-tests/touch-events/multi-touch-interfaces-manual.html +++ /dev/null @@ -1,274 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- - Test cases for Touch Events v1 Recommendation - http://www.w3.org/TR/touch-events/ - - These tests are based on Mozilla-Nokia-Google's single-touch - tests and to some extent Olli Pettay's multi-touch tests. - - The primary purpose of the tests in this document is checking that the - various interfaces of the Touch Events APIs are correctly implemented. - Other interactions are covered in other test files. - - This document references Test Assertions (abbrev TA below) written by Cathy Chan - http://www.w3.org/2010/webevents/wiki/TestAssertions ---> - -<head> -<title>Touch Events Multi-Touch Interface Tests</title> -<meta name="viewport" content="width=device-width"> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<script> - setup({explicit_done: true}); - - // Check a Touch object's atttributes for existence and correct type - // TA: 1.1.2, 1.1.3 - function check_Touch_object (t) { - test(function() { - assert_equals(Object.prototype.toString.call(t), "[object Touch]", "touch is of type Touch"); - }, "touch point is a Touch object"); - [ - ["long", "identifier"], - ["EventTarget", "target"], - ["long", "screenX"], - ["long", "screenY"], - ["long", "clientX"], - ["long", "clientY"], - ["long", "pageX"], - ["long", "pageY"], - ].forEach(function(attr) { - var type = attr[0]; - var name = attr[1]; - - // existence check - test(function() { - assert_true(name in t, name + " attribute in Touch object"); - }, "Touch." + name + " attribute exists"); - - // type check - switch(type) { - case "long": - test(function() { - assert_equals(typeof t[name], "number", name + " attribute of type long"); - }, "Touch." + name + " attribute is of type number (long)"); - break; - case "EventTarget": - // An event target is some type of Element - test(function() { - assert_true(t[name] instanceof Element, "EventTarget must be an Element."); - }, "Touch." + name + " attribute is of type Element"); - break; - default: - break; - } - }); - } - - // Check a TouchList object's attributes and methods for existence and proper type - // Also make sure all of the members of the list are Touch objects - // TA: 1.2.1, 1.2.2, 1.2.5, 1.2.6 - function check_TouchList_object (tl) { - test(function() { - assert_equals(Object.prototype.toString.call(tl), "[object TouchList]", "touch list is of type TouchList"); - }, "touch list is a TouchList object"); - [ - ["unsigned long", "length"], - ["function", "item"], - ].forEach(function(attr) { - var type = attr[0]; - var name = attr[1]; - - // existence check - test(function() { - assert_true(name in tl, name + " attribute in TouchList"); - }, "TouchList." + name + " attribute exists"); - - // type check - switch(type) { - case "unsigned long": - test(function() { - assert_equals(typeof tl[name], "number", name + " attribute of type long"); - }, "TouchList." + name + " attribute is of type number (unsigned long)"); - break; - case "function": - test(function() { - assert_equals(typeof tl[name], "function", name + " attribute of type function"); - }, "TouchList." + name + " attribute is of type function"); - break; - default: - break; - } - }); - // Each member of tl should be a proper Touch object - for (var i=0; i < tl.length; i++) { - check_Touch_object(tl.item(i)); - } - } - - // Check a TouchEvent event's attributes for existence and proper type - // Also check that each of the event's TouchList objects are valid - // TA: 1.{3,4,5}.1.1, 1.{3,4,5}.1.2 - function check_TouchEvent(ev) { - test(function() { - assert_true(ev instanceof TouchEvent, "event is a TouchEvent event"); - }, ev.type + " event is a TouchEvent event"); - [ - ["TouchList", "touches"], - ["TouchList", "targetTouches"], - ["TouchList", "changedTouches"], - ["boolean", "altKey"], - ["boolean", "metaKey"], - ["boolean", "ctrlKey"], - ["boolean", "shiftKey"], - ].forEach(function(attr) { - var type = attr[0]; - var name = attr[1]; - - // existence check - test(function() { - assert_true(name in ev, name + " attribute in " + ev.type + " event"); - }, ev.type + "." + name + " attribute exists"); - - // type check - switch(type) { - case "boolean": - test(function() { - assert_equals(typeof ev[name], "boolean", name + " attribute of type boolean"); - }, ev.type + "." + name + " attribute is of type boolean"); - break; - case "TouchList": - test(function() { - assert_equals(Object.prototype.toString.call(ev[name]), "[object TouchList]", name + " attribute of type TouchList"); - }, ev.type + "." + name + " attribute is of type TouchList"); - // Now check the validity of the TouchList - check_TouchList_object(ev[name]); - break; - default: - break; - } - }); - } - - function is_touch_over_element(touch, element) { - var bounds = element.getBoundingClientRect(); - return touch.pageX >= bounds.left && touch.pageX <= bounds.right && - touch.pageY >= bounds.top && touch.pageY <= bounds.bottom; - } - - function check_touch_clientXY(touch) { - assert_equals(touch.clientX, touch.pageX - window.pageXOffset, "touch.clientX is touch.pageX - window.pageXOffset."); - assert_equals(touch.clientY, touch.pageY - window.pageYOffset, "touch.clientY is touch.pageY - window.pageYOffset."); - } - - function run() { - var target0 = document.getElementById("target0"); - var target1 = document.getElementById("target1"); - - var test_touchstart = async_test("touchstart event received"); - var test_touchmove = async_test("touchmove event received"); - var test_touchend = async_test("touchend event received"); - var test_mousedown = async_test("Interaction with mouse events"); - - var touchstart_received = 0; - var touchmove_received = false; - var touchend_received = false; - var invalid_touchmove_received = false; - - on_event(target0, "touchstart", function onTouchStart(ev) { - ev.preventDefault(); - - if(!touchstart_received) { - // Check event ordering TA: 1.6.2 - test_touchstart.step(function() { - assert_false(touchmove_received, "touchstart precedes touchmove"); - assert_false(touchend_received, "touchstart precedes touchend"); - }); - test_touchstart.done(); - test_mousedown.done(); // If we got here, then the mouse event test is not needed. - } - - if(++touchstart_received <= 2) - check_TouchEvent(ev); - }); - - on_event(target0, "touchmove", function onTouchMove(ev) { - ev.preventDefault(); - - if (touchmove_received) - return; - touchmove_received = true; - - test_touchmove.step(function() { - assert_true(touchstart_received>0, "touchmove follows touchstart"); - assert_false(touchend_received, "touchmove precedes touchend"); - }); - test_touchmove.done(); - - check_TouchEvent(ev); - }); - - on_event(target1, "touchmove", function onTouchMove(ev) { - invalid_touchmove_received = true; - }); - - on_event(window, "touchend", function onTouchEnd(ev) { - touchend_received = true; - - test_touchend.step(function() { - assert_true(touchstart_received>0, "touchend follows touchstart"); - assert_true(touchmove_received, "touchend follows touchmove"); - assert_false(invalid_touchmove_received, "touchmove dispatched to correct target"); - }); - test_touchend.done(); - - check_TouchEvent(ev); - done(); - }); - - on_event(target0, "mousedown", function onMouseDown(ev) { - test_mousedown.step(function() { - assert_true(touchstart_received, - "The touchstart event must be dispatched before any mouse " + - "events. (If this fails, it might mean that the user agent does " + - "not implement W3C touch events at all.)" - ); - }); - test_mousedown.done(); - - if (!touchstart_received) { - // Abort the tests. If touch events are not supported, then most of - // the other event handlers will never be called, and the test will - // time out with misleading results. - done(); - } - }); - } -</script> -<style> - div { - margin: 0em; - padding: 2em; - } - #target0 { - background: yellow; - border: 1px solid orange; - } - #target1 { - background: lightblue; - border: 1px solid blue; - } -</style> -</head> -<body onload="run()"> - <h1>Touch Events: multi-touch interface tests</h1> - <div id="target0"> - Touch this box with one finger, then another one... - </div> - <div id="target1"> - ...then drag to this box and lift your fingers. - </div> - <div id="log"></div> -</body> -</html> diff --git a/tests/wpt/web-platform-tests/touch-events/multi-touch-interfaces.html b/tests/wpt/web-platform-tests/touch-events/multi-touch-interfaces.html new file mode 100644 index 00000000000..fae884480fe --- /dev/null +++ b/tests/wpt/web-platform-tests/touch-events/multi-touch-interfaces.html @@ -0,0 +1,293 @@ +<!DOCTYPE HTML> +<html> +<!-- + Test cases for Touch Events v1 Recommendation + http://www.w3.org/TR/touch-events/ + + These tests are based on Mozilla-Nokia-Google's single-touch + tests and to some extent Olli Pettay's multi-touch tests. + + The primary purpose of the tests in this document is checking that the + various interfaces of the Touch Events APIs are correctly implemented. + Other interactions are covered in other test files. + + This document references Test Assertions (abbrev TA below) written by Cathy Chan + http://www.w3.org/2010/webevents/wiki/TestAssertions +--> + +<head> +<title>Touch Events Multi-Touch Interface Tests</title> +<meta name="viewport" content="width=device-width"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script> + setup({explicit_done: true}); + + // Check a Touch object's atttributes for existence and correct type + // TA: 1.1.2, 1.1.3 + function check_Touch_object (t, element) { + test(function() { + assert_equals(Object.prototype.toString.call(t), "[object Touch]", "touch is of type Touch"); + }, element + "'s touch point is a Touch object"); + [ + ["long", "identifier"], + ["EventTarget", "target"], + ["long", "screenX"], + ["long", "screenY"], + ["long", "clientX"], + ["long", "clientY"], + ["long", "pageX"], + ["long", "pageY"], + ].forEach(function(attr) { + var type = attr[0]; + var name = attr[1]; + + // existence check + test(function() { + assert_true(name in t, name + " attribute in Touch object"); + }, element + ".Touch." + name + " attribute exists"); + + // type check + switch(type) { + case "long": + test(function() { + assert_equals(typeof t[name], "number", name + " attribute of type long"); + }, element + ".Touch." + name + " attribute is of type number (long)"); + break; + case "EventTarget": + // An event target is some type of Element + test(function() { + assert_true(t[name] instanceof Element, "EventTarget must be an Element."); + }, element + ".Touch." + name + " attribute is of type Element"); + break; + default: + break; + } + }); + } + + // Check a TouchList object's attributes and methods for existence and proper type + // Also make sure all of the members of the list are Touch objects + // TA: 1.2.1, 1.2.2, 1.2.5, 1.2.6 + function check_TouchList_object (tl, element) { + test(function() { + assert_equals(Object.prototype.toString.call(tl), "[object TouchList]", "touch list is of type TouchList"); + }, element + "'s touch list is a TouchList object"); + [ + ["unsigned long", "length"], + ["function", "item"], + ].forEach(function(attr) { + var type = attr[0]; + var name = attr[1]; + + // existence check + test(function() { + assert_true(name in tl, name + " attribute in TouchList"); + }, element + ".TouchList." + name + " attribute exists"); + + // type check + switch(type) { + case "unsigned long": + test(function() { + assert_equals(typeof tl[name], "number", name + " attribute of type long"); + }, element + ".TouchList." + name + " attribute is of type number (unsigned long)"); + break; + case "function": + test(function() { + assert_equals(typeof tl[name], "function", name + " attribute of type function"); + }, element + ".TouchList." + name + " attribute is of type function"); + break; + default: + break; + } + }); + // Each member of tl should be a proper Touch object + for (var i=0; i < tl.length; i++) { + check_Touch_object(tl.item(i), element + "[" + i + "]"); + } + } + + // Check a TouchEvent event's attributes for existence and proper type + // Also check that each of the event's TouchList objects are valid + // TA: 1.{3,4,5}.1.1, 1.{3,4,5}.1.2 + function check_TouchEvent(ev, touchstart_count) { + test(function() { + assert_true(ev instanceof TouchEvent, "event is a TouchEvent event"); + }, ev.type + touchstart_count + " event is a TouchEvent event"); + [ + ["TouchList", "touches"], + ["TouchList", "targetTouches"], + ["TouchList", "changedTouches"], + ["boolean", "altKey"], + ["boolean", "metaKey"], + ["boolean", "ctrlKey"], + ["boolean", "shiftKey"], + ].forEach(function(attr) { + var type = attr[0]; + var name = attr[1]; + + // existence check + test(function() { + assert_true(name in ev, name + " attribute in " + ev.type + " event"); + }, ev.type + touchstart_count + "." + name + " attribute exists"); + + // type check + switch(type) { + case "boolean": + test(function() { + assert_equals(typeof ev[name], "boolean", name + " attribute of type boolean"); + }, ev.type + touchstart_count + "." + name + " attribute is of type boolean"); + break; + case "TouchList": + test(function() { + assert_equals(Object.prototype.toString.call(ev[name]), "[object TouchList]", name + " attribute of type TouchList"); + }, ev.type + touchstart_count + "." + name + " attribute is of type TouchList"); + // Now check the validity of the TouchList + check_TouchList_object(ev[name], ev.type + touchstart_count + "." + name); + break; + default: + break; + } + }); + } + + function is_touch_over_element(touch, element) { + var bounds = element.getBoundingClientRect(); + return touch.pageX >= bounds.left && touch.pageX <= bounds.right && + touch.pageY >= bounds.top && touch.pageY <= bounds.bottom; + } + + function check_touch_clientXY(touch) { + assert_equals(touch.clientX, touch.pageX - window.pageXOffset, "touch.clientX is touch.pageX - window.pageXOffset."); + assert_equals(touch.clientY, touch.pageY - window.pageYOffset, "touch.clientY is touch.pageY - window.pageYOffset."); + } + + function run() { + var target0 = document.getElementById("target0"); + var target1 = document.getElementById("target1"); + + var test_touchstart = async_test("touchstart event received"); + var test_touchmove = async_test("touchmove event received"); + var test_touchend = async_test("touchend event received"); + var test_mousedown = async_test("Interaction with mouse events"); + + var touchstart_received = 0; + var touchmove_received = false; + var touchend_received = false; + var invalid_touchmove_received = false; + var actions_promise; + + on_event(target0, "touchstart", function onTouchStart(ev) { + ev.preventDefault(); + + if(!touchstart_received) { + // Check event ordering TA: 1.6.2 + test_touchstart.step(function() { + assert_false(touchmove_received, "touchstart precedes touchmove"); + assert_false(touchend_received, "touchstart precedes touchend"); + }); + test_touchstart.done(); + test_mousedown.done(); // If we got here, then the mouse event test is not needed. + } + + if(++touchstart_received <= 2) + check_TouchEvent(ev, touchstart_received); + }); + + on_event(target0, "touchmove", function onTouchMove(ev) { + ev.preventDefault(); + + if (touchmove_received) + return; + touchmove_received = true; + + test_touchmove.step(function() { + assert_true(touchstart_received>0, "touchmove follows touchstart"); + assert_false(touchend_received, "touchmove precedes touchend"); + }); + test_touchmove.done(); + + check_TouchEvent(ev); + }); + + on_event(target1, "touchmove", function onTouchMove(ev) { + invalid_touchmove_received = true; + }); + + on_event(window, "touchend", function onTouchEnd(ev) { + touchend_received = true; + + test_touchend.step(function() { + assert_true(touchstart_received>0, "touchend follows touchstart"); + assert_true(touchmove_received, "touchend follows touchmove"); + assert_false(invalid_touchmove_received, "touchmove dispatched to correct target"); + }); + test_touchend.done(); + + check_TouchEvent(ev); + done(); + }); + + on_event(target0, "mousedown", function onMouseDown(ev) { + test_mousedown.step(function() { + assert_true(touchstart_received, + "The touchstart event must be dispatched before any mouse " + + "events. (If this fails, it might mean that the user agent does " + + "not implement W3C touch events at all.)" + ); + }); + test_mousedown.done(); + + if (!touchstart_received) { + // Abort the tests. If touch events are not supported, then most of + // the other event handlers will never be called, and the test will + // time out with misleading results. + done(); + } + }); + + actions_promise = new test_driver.Actions() + .addPointer("touchPointer1", "touch") + .addPointer("touchPointer2", "touch") + .pointerMove(0, 0, {origin: target0, sourceName: "touchPointer1"}) + .pointerMove(3, 0, {origin: target0, sourceName: "touchPointer2"}) + .pointerDown({sourceName: "touchPointer1"}) + .pointerDown({sourceName: "touchPointer2"}) + .pointerMove(0, 10, {origin: target0, sourceName: "touchPointer1"}) + .pointerMove(3, 10, {origin: target0, sourceName: "touchPointer2"}) + .pointerMove(0, 0, {origin: target1, sourceName: "touchPointer1"}) + .pointerMove(3, 0, {origin: target1, sourceName: "touchPointer2"}) + .pointerUp({sourceName: "touchPointer1"}) + .pointerUp({sourceName: "touchPointer2"}) + .send(); + } +</script> +<style> + div { + margin: 0em; + padding: 2em; + } + #target0 { + background: yellow; + border: 1px solid orange; + } + #target1 { + background: lightblue; + border: 1px solid blue; + } +</style> +</head> +<body onload="run()"> + <h1>Touch Events: multi-touch interface tests</h1> + <div id="target0"> + Touch this box with one finger, then another one... + </div> + <div id="target1"> + ...then drag to this box and lift your fingers. + </div> + <div id="log"></div> +</body> +</html> diff --git a/tests/wpt/web-platform-tests/touch-events/single-touch.html b/tests/wpt/web-platform-tests/touch-events/single-touch.html index a067bc13801..4db96c208d6 100644 --- a/tests/wpt/web-platform-tests/touch-events/single-touch.html +++ b/tests/wpt/web-platform-tests/touch-events/single-touch.html @@ -171,7 +171,7 @@ assert_greater_than_equal(touch.pageY, 0, "touch.pageY is no less than 0"); } - function run() { + async function run() { var target0 = document.getElementById("target0"); var target1 = document.getElementById("target1"); @@ -185,7 +185,6 @@ var touchend_received = false; var invalid_touchmove_received = false; var touchstart_identifier; - var actions_promise; on_event(target0, "touchstart", function onTouchStart(ev) { ev.preventDefault(); @@ -242,7 +241,7 @@ }, "touchstart: touch screenX/screenY pageX/pageY and clientX/clientY values are no less than 0"); }); - target0.ontouchmove = function (ev) { + on_event(target0, "touchmove", function ontouchmove(ev) { ev.preventDefault(); if (touchmove_received) @@ -289,13 +288,13 @@ check_screenXY_clientXY_pageXY(tt); }, "touchmove: touch screenX/screenY pageX/pageY and clientX/clientY values are no less than 0"); - }; + }); on_event(target1, "touchmove", function onTouchMove(ev) { invalid_touchmove_received = true; }); - window.ontouchend = function(ev) { + on_event(window, "touchend", function ontouchend(ev) { touchend_received = true; test_touchend.step(function() { @@ -335,7 +334,7 @@ }, "touchend: touch screenX/screenY pageX/pageY and clientX/clientY values are no less than 0"); done(); - }; + }); on_event(target0, "mousedown", function onMouseDown(ev) { test_mousedown.step(function() { @@ -355,7 +354,7 @@ } }); - actions_promise = new test_driver.Actions() + await new test_driver.Actions() .addPointer("touchPointer1", "touch") .pointerMove(0, 0, {origin: target0, sourceName: "touchPointer1"}) .pointerDown({sourceName: "touchPointer1"}) @@ -390,4 +389,4 @@ </div> <div id="log"></div> </body> -</html> +</html>
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html b/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html index fc98f5cbc58..ae5ac25052d 100644 --- a/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html +++ b/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html @@ -10,7 +10,7 @@ Set-Cookie: trusted-types-reporting-check-report={{$id:uuid()}}; Path=/trusted-types/ Content-Security-Policy-Report-Only: \ trusted-types one two; \ - report-uri ../content-security-policy/support/report.py?op=put&reportID={{$id}} + report-uri /reporting/resources/report.py?op=put&reportID={{$id}} --> </head> <body> diff --git a/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html.sub.headers b/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html.sub.headers index 5830239f5ca..c055bdc6563 100644 --- a/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html.sub.headers +++ b/tests/wpt/web-platform-tests/trusted-types/trusted-types-reporting-check-report.html.sub.headers @@ -3,4 +3,4 @@ Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0, false Pragma: no-cache Set-Cookie: trusted-types-reporting-check-report={{$id:uuid()}}; Path=/trusted-types/ -Content-Security-Policy-Report-Only: trusted-types one two; report-uri ../content-security-policy/support/report.py?op=put&reportID={{$id}} +Content-Security-Policy-Report-Only: trusted-types one two; report-uri /reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/tests/wpt/web-platform-tests/trusted-types/trusted-types-tojson.tentative.html b/tests/wpt/web-platform-tests/trusted-types/trusted-types-tojson.tentative.html new file mode 100644 index 00000000000..72c53830994 --- /dev/null +++ b/tests/wpt/web-platform-tests/trusted-types/trusted-types-tojson.tentative.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <meta http-equiv="Content-Security-Policy" content="trusted-types foo"> +</head> +<body> +<script> +test(t => { + let policy = trustedTypes.createPolicy("foo", { + createHTML: h => h, + createScript: s => s, + createScriptURL: u => u, + }); + + assert_equals(JSON.stringify({"x": policy.createHTML("<p>foo</p>")}), + "{\"x\":\"<p>foo</p>\"}"); + assert_equals(JSON.stringify({"x": policy.createScript("foo(bar)")}), + "{\"x\":\"foo(bar)\"}"); + assert_equals(JSON.stringify({"x": policy.createScriptURL("https://foo.example.com/bar")}), + "{\"x\":\"https://foo.example.com/bar\"}"); +}, "toJSON"); +</script> +</body> diff --git a/tests/wpt/web-platform-tests/uievents/interface/click-event.htm b/tests/wpt/web-platform-tests/uievents/interface/click-event.htm index 8542f98cb13..ff78ae95a08 100644 --- a/tests/wpt/web-platform-tests/uievents/interface/click-event.htm +++ b/tests/wpt/web-platform-tests/uievents/interface/click-event.htm @@ -13,12 +13,17 @@ var clicktarget = document.querySelector("#clicktarget"); var t = async_test("synthetic click event is a PointerEvent"); clicktarget.addEventListener('click', t.step_func(function (e) { - assert_equals(e.constructor, window.PointerEvent); - assert_true(e instanceof window.PointerEvent); + assert_equals(e.constructor, window.PointerEvent, "Click is a PointerEvent"); + assert_true(e instanceof window.PointerEvent, "Click is an instance of PointerEvent"); // Since this click is not generated by a pointing device, pointerId and // pointerType must have default values (0 and empty string) - assert_equals(e.pointerId, 0); - assert_equals(e.pointerType, ""); + assert_equals(e.pointerId, 0, "Click's pointerId has the default value of 0"); + assert_equals(e.pointerType, "", "Click's pointerType has the default value of empty string"); + assert_equals(e.screenX, 0, "Click's screenX coordinate should not be set."); + assert_equals(e.screenY, 0, "Click's screenY coordinate should not be set."); + assert_equals(e.clientX, 0, "Click's clientX coordinate should not be set."); + assert_equals(e.clientY, 0, "Click's clientY coordinate should not be set."); + assert_equals(e.detail, 0, "element.click click event should not populate click count"); t.done(); })); document.querySelector('#clicktarget').click(); diff --git a/tests/wpt/web-platform-tests/uievents/interface/keyboard-accesskey-click-event.html b/tests/wpt/web-platform-tests/uievents/interface/keyboard-accesskey-click-event.html new file mode 100644 index 00000000000..74c64101350 --- /dev/null +++ b/tests/wpt/web-platform-tests/uievents/interface/keyboard-accesskey-click-event.html @@ -0,0 +1,81 @@ +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<p>Tests that a keyboard access key to press a button will fire only the click event</p> +<button id="button" accesskey="g">Click Me with Shift+Alt+g or on Mac with Control+Option+g</button> +<input id="inputbutton" type="button" accesskey="b" value="Click me with Shift+Alt+b or on Mac with Control+Option+b"> + +<script> +let button = document.getElementById("button"); +let inputbutton = document.getElementById("inputbutton"); +let radiobutton = document.getElementById("radiobutton"); +let elementList = [button, inputbutton]; +let eventLog = []; +const eventList = ["pointerdown", "pointerup", "mousedown", "mouseup", "click"]; +elementList.forEach((el)=>{eventList.forEach((ev)=>el.addEventListener(ev, (e)=>{ + eventLog.push(`${ev}_${el.id}`); + if(ev === "click" && currentTest){ + currentTest.step(()=>{ + if(e instanceof PointerEvent){ + // We want the test to run on all browsers even if click is not a PointerEvent. + assert_equals(e.pointerId, 0, "Click's pointerId has default value"); + assert_equals(e.pointerType, "", "Click's pointerType has default value"); + } + }); + } +}));}); +let currentTest; +let testElements = [button, inputbutton]; +let accesskeyMap = new Map([[button, "g"], [inputbutton, "b"]]); +testElements.forEach((el)=>promise_test((test)=> new Promise(async (resolve,reject)=>{ + currentTest = test; + eventLog = []; + var eventWatcher = new EventWatcher(test, el, ['click']); + let waitForClick = eventWatcher.wait_for('click'); + let actions = new test_driver.Actions(); + actions = pressAccessKey(actions, accesskeyMap.get(el)); + await actions.send(); + await waitForClick; + + assert_array_equals(eventLog, [`click_${el.id}`], "The Keyboard generated click only sends the click event."); + resolve(); +}), `Test that the Keyboard generated click does not fire pointer or mouse events for ${el.id}`)); + +function pressAccessKey(actions, accessKey){ + // TODO(liviutinta): figure out which values for controlKey/optionKey to use for Mac + let controlKey = '\uE009'; // left Control key + let altKey = '\uE00A'; // left Alt key + let optionKey = altKey; // left Option key + let shiftKey = '\uE008'; // left Shift key + // There are differences in using accesskey across browsers and OS's. + // See: // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey + let isMacOSX = navigator.userAgent.indexOf("Mac") != -1; + let isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; + // Default OS access keys. + let osAccessKey = [shiftKey, altKey]; + // Set the OS keys that need to be pressed for a keyboard accessibility click. + if(isMacOSX){ + if(isFirefox){ + // On Firefox on Mac use Shift + Option + accesskey. + osAccessKey = [shiftKey, optionKey]; + }else{ + // On Mac use Control + Option + accesskey (for button is g). + osAccessKey = [controlKey, optionKey]; + } + } + // Press keys. + for(key of osAccessKey) + actions = actions.keyDown(key); + actions = actions + .keyDown(accessKey) + .addTick() + .keyUp(accessKey); + osAccessKey.reverse(); + for(key of osAccessKey) + actions = actions.keyUp(key); + return actions; +} +</script> diff --git a/tests/wpt/web-platform-tests/uievents/interface/keyboard-click-event.html b/tests/wpt/web-platform-tests/uievents/interface/keyboard-click-event.html new file mode 100644 index 00000000000..d70b8cce6ab --- /dev/null +++ b/tests/wpt/web-platform-tests/uievents/interface/keyboard-click-event.html @@ -0,0 +1,50 @@ +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<p>Tests that a button pressed by using Space or Enter will fire only the click event</p> +<button id="button">Click Me by pressing Enter or Space</button> +<input id="inputbutton" type="button" value="Click me by pressing Enter or Space"> + +<script> +let button = document.getElementById("button"); +let inputbutton = document.getElementById("inputbutton"); +let radiobutton = document.getElementById("radiobutton"); +let elementList = [button, inputbutton]; +let eventLog = []; +const enterKey = '\uE006'; +const spaceKey = ' '; +let keys = [spaceKey, enterKey]; +const eventList = ["pointerdown", "pointerup", "mousedown", "mouseup", "click"]; +elementList.forEach((el)=>{eventList.forEach((ev)=>el.addEventListener(ev, (e)=>{ + eventLog.push(`${ev}_${el.id}`); + if(ev === "click" && currentTest){ + currentTest.step(()=>{ + if(e instanceof PointerEvent){ + // We want the test to run on all browsers even if click is not a PointerEvent. + assert_equals(e.pointerId, 0, "Click's pointerId has default value"); + assert_equals(e.pointerType, "", "Click's pointerType has default value"); + } + }); + } +}));}); +let currentTest; +let testElements = [button, inputbutton]; +let keyNameMap = new Map([[spaceKey, "space"], [enterKey, "enter"]]); +keys.forEach((key)=>testElements.forEach((el)=>promise_test((test)=> new Promise(async (resolve,reject)=>{ + currentTest = test; + eventLog = []; + var eventWatcher = new EventWatcher(test, el, ['click']); + let waitForClick = eventWatcher.wait_for('click'); + el.focus(); + let actions = new test_driver.Actions(); + await test_driver + .send_keys(el, key); + await waitForClick; + + assert_array_equals(eventLog, [`click_${el.id}`], "The Keyboard generated click only sends the click event."); + resolve(); +}), `Test that the Keyboard generated click does not fire pointer or mouse events for ${el.id} when pressing ${keyNameMap.get(key)} key`))); +</script> diff --git a/tests/wpt/web-platform-tests/uievents/keyboard/modifier-keys-combinations.html b/tests/wpt/web-platform-tests/uievents/keyboard/modifier-keys-combinations.html new file mode 100644 index 00000000000..1b364ff72ce --- /dev/null +++ b/tests/wpt/web-platform-tests/uievents/keyboard/modifier-keys-combinations.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>UI Events Test: Modifier keys combinations</title> +<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com"> +<link rel="help" href="https://w3c.github.io/uievents/#idl-keyboardevent" /> +<meta name="assert" content="This test checks that modifier keys combinations are properly detected in 'keydown' event."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<div id="target" tabindex="0">Target</div> +<script> + const keys = { + "Shift": '\uE008' + 'y', + "Control": '\uE009' + 'y', + "Alt": '\uE00A' + 'y', + "Meta": '\uE03D' + 'y', + }; + + target.focus(); + for (const [key, code] of Object.entries(keys)) { + promise_test(() => { + return new Promise(resolve => { + target.addEventListener("keydown", (event) => { + if (event.key != key) + resolve(event); + }); + test_driver.send_keys(target, code); + }).then((event) => { + if (event.shiftKey) { + // Shift + y will send a "Y" keydown event on Chromium and Firefox, but a "y" one on WebKit. + assert_true(event.key == "y" || event.key == "Y"); + } else { + assert_equals(event.key, "y"); + } + assert_equals(event.shiftKey, key === "Shift"); + assert_equals(event.ctrlKey, key === "Control"); + assert_equals(event.altKey, key === "Alt"); + assert_equals(event.metaKey, key === "Meta"); + }); + }, `Check sending "${key} + y" key combination`); + } +</script> diff --git a/tests/wpt/web-platform-tests/uievents/keyboard/modifier-keys.html b/tests/wpt/web-platform-tests/uievents/keyboard/modifier-keys.html new file mode 100644 index 00000000000..635e5d3b779 --- /dev/null +++ b/tests/wpt/web-platform-tests/uievents/keyboard/modifier-keys.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>UI Events Test: Modifier keys</title> +<link rel="author" title="Manuel Rego Casasnovas" href="mailto:rego@igalia.com"> +<link rel="help" href="https://w3c.github.io/uievents/#idl-keyboardevent" /> +<meta name="assert" content="This test checks that modifier keys are properly detected in 'keydown' event."> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<div id="target" tabindex="0">Target</div> +<script> + const keys = { + "Shift": '\uE008', + "Control": '\uE009', + "Alt": '\uE00A', + "Meta": '\uE03D', + }; + + target.focus(); + for (const [key, code] of Object.entries(keys)) { + promise_test(() => { + return new Promise(resolve => { + target.addEventListener("keydown", resolve); + test_driver.send_keys(target, code); + }).then((event) => { + assert_equals(event.key, key); + assert_equals(event.shiftKey, key === "Shift"); + assert_equals(event.ctrlKey, key === "Control"); + assert_equals(event.altKey, key === "Alt"); + assert_equals(event.metaKey, key === "Meta"); + }); + }, `Check sending ${key} key`); + } +</script> diff --git a/tests/wpt/web-platform-tests/wasm/jsapi/global/type.tentative.any.js b/tests/wpt/web-platform-tests/wasm/jsapi/global/type.tentative.any.js index 318b1ac578c..a1cfae90db9 100644 --- a/tests/wpt/web-platform-tests/wasm/jsapi/global/type.tentative.any.js +++ b/tests/wpt/web-platform-tests/wasm/jsapi/global/type.tentative.any.js @@ -3,9 +3,10 @@ function assert_type(argument) { const myglobal = new WebAssembly.Global(argument); + const globaltype = myglobal.type(); - assert_equals(myglobal.type.value, argument.value); - assert_equals(myglobal.type.mutable, argument.mutable); + assert_equals(globaltype.value, argument.value); + assert_equals(globaltype.mutable, argument.mutable); } test(() => { @@ -58,7 +59,7 @@ test(() => { test(() => { const myglobal = new WebAssembly.Global({"value": "i32", "mutable": true}); - const propertyNames = Object.getOwnPropertyNames(myglobal.type); + const propertyNames = Object.getOwnPropertyNames(myglobal.type()); assert_equals(propertyNames[0], "mutable"); assert_equals(propertyNames[1], "value"); }, "key ordering"); diff --git a/tests/wpt/web-platform-tests/wasm/jsapi/memory/type.tentative.any.js b/tests/wpt/web-platform-tests/wasm/jsapi/memory/type.tentative.any.js index efe9e2a37de..a96a3227adc 100644 --- a/tests/wpt/web-platform-tests/wasm/jsapi/memory/type.tentative.any.js +++ b/tests/wpt/web-platform-tests/wasm/jsapi/memory/type.tentative.any.js @@ -3,11 +3,12 @@ function assert_type(argument) { const memory = new WebAssembly.Memory(argument); + const memorytype = memory.type() - assert_equals(memory.type.minimum, argument.minimum); - assert_equals(memory.type.maximum, argument.maximum); + assert_equals(memorytype.minimum, argument.minimum); + assert_equals(memorytype.maximum, argument.maximum); if (argument.shared !== undefined) { - assert_equals(memory.type.shared, argument.shared); + assert_equals(memorytype.shared, argument.shared); } } diff --git a/tests/wpt/web-platform-tests/wasm/jsapi/table/type.tentative.any.js b/tests/wpt/web-platform-tests/wasm/jsapi/table/type.tentative.any.js new file mode 100644 index 00000000000..e1130e0ad02 --- /dev/null +++ b/tests/wpt/web-platform-tests/wasm/jsapi/table/type.tentative.any.js @@ -0,0 +1,26 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/assertions.js + +function assert_type(argument) { + const mytable = new WebAssembly.Table(argument); + const tabletype = mytable.type() + assert_equals(tabletype.minimum, argument.minimum); + assert_equals(tabletype.maximum, argument.maximum); + assert_equals(tabletype.element, argument.element); +} + +test(() => { + assert_type({ "minimum": 0, "element": "funcref"}); +}, "Zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 5, "element": "funcref" }); +}, "Non-zero initial, no maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 0, "element": "funcref" }); +}, "Zero maximum"); + +test(() => { + assert_type({ "minimum": 0, "maximum": 5, "element": "funcref" }); +}, "None-zero maximum"); diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin-no-cors.har b/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin-no-cors.har index 96d0b4e453e..983024bf63f 100644 --- a/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin-no-cors.har +++ b/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin-no-cors.har @@ -4,7 +4,7 @@ { "request": { "method": "GET", - "url": "https://web-platform.test:8444/web-bundle/resources/wbn/no-cors/resource.cors.json", + "url": "https://web-platform.test:8444/web-bundle/resources/wbn/no-cors/resource.cors.js", "headers": [] }, "response": { @@ -12,7 +12,7 @@ "headers": [ { "name": "Content-type", - "value": "application/json" + "value": "text/javascript" }, { "name": "Access-Control-Allow-Origin", @@ -20,14 +20,14 @@ } ], "content": { - "text": "{ cors: 1 }" + "text": "scriptLoaded('resource.cors.js');" } } }, { "request": { "method": "GET", - "url": "https://web-platform.test:8444/web-bundle/resources/wbn/no-cors/resource.no-cors.json", + "url": "https://web-platform.test:8444/web-bundle/resources/wbn/no-cors/resource.no-cors.js", "headers": [] }, "response": { @@ -35,11 +35,11 @@ "headers": [ { "name": "Content-type", - "value": "application/json" + "value": "text/javascript" } ], "content": { - "text": "{ no_cors: 1 }" + "text": "scriptLoaded('resource.no-cors.js');" } } } diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin.har b/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin.har index 7435393b13b..2197dea3beb 100644 --- a/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin.har +++ b/tests/wpt/web-platform-tests/web-bundle/resources/cross-origin.har @@ -4,7 +4,7 @@ { "request": { "method": "GET", - "url": "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.json", + "url": "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.js", "headers": [] }, "response": { @@ -12,7 +12,7 @@ "headers": [ { "name": "Content-type", - "value": "application/json" + "value": "text/javascript" }, { "name": "Access-Control-Allow-Origin", @@ -20,14 +20,14 @@ } ], "content": { - "text": "{ cors: 1 }" + "text": "scriptLoaded('resource.cors.js');" } } }, { "request": { "method": "GET", - "url": "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.json", + "url": "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.js", "headers": [] }, "response": { @@ -35,11 +35,11 @@ "headers": [ { "name": "Content-type", - "value": "application/json" + "value": "text/javascript" } ], "content": { - "text": "{ no_cors: 1}" + "text": "scriptLoaded('resource.no-cors.js');" } } } diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/generate-test-wbns.sh b/tests/wpt/web-platform-tests/web-bundle/resources/generate-test-wbns.sh index 5a205c9d5ce..36a8078194f 100755 --- a/tests/wpt/web-platform-tests/web-bundle/resources/generate-test-wbns.sh +++ b/tests/wpt/web-platform-tests/web-bundle/resources/generate-test-wbns.sh @@ -81,11 +81,11 @@ gen-bundle \ gen-bundle \ -version b1 \ -har cross-origin.har \ - -primaryURL $wpt_test_https_origin/web-bundle/resources/wbn/cors/resource.cors.json \ + -primaryURL $wpt_test_https_origin/web-bundle/resources/wbn/cors/resource.cors.js \ -o wbn/cors/cross-origin.wbn gen-bundle \ -version b1 \ -har cross-origin-no-cors.har \ - -primaryURL $wpt_test_https_origin/web-bundle/resources/wbn/no-cors/resource.cors.json \ + -primaryURL $wpt_test_https_origin/web-bundle/resources/wbn/no-cors/resource.cors.js \ -o wbn/no-cors/cross-origin.wbn diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/test-helpers.js b/tests/wpt/web-platform-tests/web-bundle/resources/test-helpers.js new file mode 100644 index 00000000000..d4f420ecf49 --- /dev/null +++ b/tests/wpt/web-platform-tests/web-bundle/resources/test-helpers.js @@ -0,0 +1,73 @@ +// Helper functions used in web-bundle tests. + +function addElementAndWaitForLoad(element) { + return new Promise((resolve, reject) => { + element.onload = resolve; + element.onerror = reject; + document.body.appendChild(element); + }); +} + +function addElementAndWaitForError(element) { + return new Promise((resolve, reject) => { + element.onload = reject; + element.onerror = resolve; + document.body.appendChild(element); + }); +} + +function fetchAndWaitForReject(url) { + return new Promise((resolve, reject) => { + fetch(url) + .then(() => { + reject(); + }) + .catch(() => { + resolve(); + }); + }); +} + +function addLinkAndWaitForLoad(url, resources, crossorigin) { + return new Promise((resolve, reject) => { + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = url; + if (crossorigin) { + link.crossOrigin = crossorigin; + } + for (const resource of resources) { + link.resources.add(resource); + } + link.onload = () => resolve(link); + link.onerror = () => reject(link); + document.body.appendChild(link); + }); +} + +function addLinkAndWaitForError(url, resources, crossorigin) { + return new Promise((resolve, reject) => { + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = url; + if (crossorigin) { + link.crossOrigin = crossorigin; + } + for (const resource of resources) { + link.resources.add(resource); + } + link.onload = () => reject(link); + link.onerror = () => resolve(link); + document.body.appendChild(link); + }); +} + +function addScriptAndWaitForError(url) { + return new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = url; + script.onload = reject; + script.onerror = resolve; + document.body.appendChild(script); + }); +} diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/urn-uuid.har b/tests/wpt/web-platform-tests/web-bundle/resources/urn-uuid.har index d821b768a60..1708a8917b6 100644 --- a/tests/wpt/web-platform-tests/web-bundle/resources/urn-uuid.har +++ b/tests/wpt/web-platform-tests/web-bundle/resources/urn-uuid.har @@ -35,7 +35,7 @@ } ], "content": { - "text": "<script> window.parent.postMessage('subframe loaded from WBN: location = ' + location.href, '*'); </script>" + "text": "<script>\nwindow.addEventListener('message', (e) =>{e.source.postMessage(eval(e.data), e.origin);});\n</script>" } } } diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/__dir__.headers b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/__dir__.headers index 383abc543c6..9d1d0b0ef2f 100644 --- a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/__dir__.headers +++ b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/__dir__.headers @@ -1,2 +1,3 @@ Content-Type: application/webbundle +X-Content-Type-Options: nosniff Access-Control-Allow-Origin: * diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/cross-origin.wbn b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/cross-origin.wbn Binary files differindex bed9bf2af74..8043ba7df2d 100644 --- a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/cross-origin.wbn +++ b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/cors/cross-origin.wbn diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/no-cors/__dir__.headers b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/no-cors/__dir__.headers new file mode 100644 index 00000000000..21e57b9caca --- /dev/null +++ b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/no-cors/__dir__.headers @@ -0,0 +1,2 @@ +Content-Type: application/webbundle +X-Content-Type-Options: nosniff diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/no-cors/cross-origin.wbn b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/no-cors/cross-origin.wbn Binary files differindex b2adba65451..7004673039a 100644 --- a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/no-cors/cross-origin.wbn +++ b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/no-cors/cross-origin.wbn diff --git a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/urn-uuid.wbn b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/urn-uuid.wbn Binary files differindex 45db6fefbbe..3255787c1c5 100644 --- a/tests/wpt/web-platform-tests/web-bundle/resources/wbn/urn-uuid.wbn +++ b/tests/wpt/web-platform-tests/web-bundle/resources/wbn/urn-uuid.wbn diff --git a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/check-cookie-and-return-bundle.py b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/check-cookie-and-return-bundle.py new file mode 100644 index 00000000000..32765e85c93 --- /dev/null +++ b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/check-cookie-and-return-bundle.py @@ -0,0 +1,25 @@ +import os + + +def main(request, response): + origin = request.headers.get(b"origin") + + if origin is not None: + response.headers.set(b"Access-Control-Allow-Origin", origin) + response.headers.set(b"Access-Control-Allow-Methods", b"GET") + response.headers.set(b"Access-Control-Allow-Credentials", b"true") + + headers = [ + (b"Content-Type", b"application/webbundle"), + (b"X-Content-Type-Options", b"nosniff"), + ] + + cookie = request.cookies.first(b"milk", None) + if (cookie is not None) and cookie.value == b"1": + with open( + os.path.join(os.path.dirname(__file__), "../resources/wbn/subresource.wbn"), + "rb", + ) as f: + return (200, headers, f.read()) + else: + return (400, [], "") diff --git a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subframe-from-web-bundle.tentative.html b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subframe-from-web-bundle.tentative.html index 1e5cdd6943e..f6f2b769d0f 100644 --- a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subframe-from-web-bundle.tentative.html +++ b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subframe-from-web-bundle.tentative.html @@ -9,27 +9,98 @@ <body> <script> -promise_test(async () => { - const frame_url = 'urn:uuid:429fcc4e-0696-4bad-b099-ee9175f023ae'; - const link = document.createElement('link'); - link.rel = 'webbundle'; - link.href = '../resources/wbn/urn-uuid.wbn'; - link.resources = frame_url; - document.body.appendChild(link); - const message_promisse = new Promise((resolve) => { - window.addEventListener('message', (e) => { - resolve(e.data); - }); +const frame_url = 'urn:uuid:429fcc4e-0696-4bad-b099-ee9175f023ae'; + +urn_uuid_iframe_test( + 'location.href', + frame_url, + 'location.href in urn uuid iframe.'); + +urn_uuid_iframe_test( + '(' + (() => { + try { + let result = window.localStorage; + return 'no error'; + } catch (e) { + return e.name; + } + }).toString() + ')()', + 'SecurityError', + 'Accesing window.localStorage should throw a SecurityError.'); + +urn_uuid_iframe_test( + '(' + (() => { + try { + let result = window.sessionStorage; + return 'no error'; + } catch (e) { + return e.name; + } + }).toString() + ')()', + 'SecurityError', + 'Accesing window.sessionStorage should throw a SecurityError.'); + +urn_uuid_iframe_test( + '(' + (() => { + try { + let result = document.cookie; + return 'no error'; + } catch (e) { + return e.name; + } + }).toString() + ')()', + 'SecurityError', + 'Accesing document.cookie should throw a SecurityError.'); + +urn_uuid_iframe_test( + '(' + (() => { + try { + let request = window.indexedDB.open("db"); + return 'no error'; + } catch (e) { + return e.name; + } + }).toString() + ')()', + 'SecurityError', + 'Opening an indexedDB should throw a SecurityError.'); + +urn_uuid_iframe_test( + 'window.caches === undefined', + true, + 'window.caches should be undefined.'); + +function urn_uuid_iframe_test(code, expected, name) { + promise_test(async () => { + const link = document.createElement('link'); + link.rel = 'webbundle'; + link.href = '../resources/wbn/urn-uuid.wbn'; + link.resources = frame_url; + document.body.appendChild(link); + const iframe = document.createElement('iframe'); + iframe.src = frame_url; + const load_promise = new Promise((resolve) => { + iframe.addEventListener('load', resolve); }); - const iframe = document.createElement('iframe'); - iframe.src = frame_url; - document.body.appendChild(iframe); - assert_equals( - await message_promisse, - 'subframe loaded from WBN: location = ' + frame_url); - document.body.removeChild(link); - document.body.removeChild(iframe); -}, "Subframe load from Web Bundle"); + document.body.appendChild(iframe); + await load_promise; + assert_equals( + await evalInIframe(iframe, code), + expected); + document.body.removeChild(link); + document.body.removeChild(iframe); + }, name); +} +async function evalInIframe(iframe, code) { + const message_promise = new Promise((resolve) => { + const listener = (e) => { + window.removeEventListener('message', listener); + resolve(e.data); + } + window.addEventListener('message', listener); + }); + iframe.contentWindow.postMessage(code,'*'); + return message_promise; +} </script> </body> diff --git a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cors-error.tentative.html b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cors-error.tentative.html new file mode 100644 index 00000000000..1ea96fa0fdd --- /dev/null +++ b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cors-error.tentative.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>Cross origin WebBundle subresource loading (error case)</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md" +/> +<link + rel="help" + href="https://html.spec.whatwg.org/multipage/#cors-settings-attribute" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <!-- + This wpt should run on an origin different from https://web-platform.test:8444/, + from where cross-orign WebBundles are served. + + This test uses a cross-origin WebBundle, + https://web-platform.test:8444/web-bundle/resources/wbn/no-cors/cross-origin.wbn, + which is served *without* an Access-Control-Allow-Origin response header. + + `cross-origin.wbn` includes two subresources: + a. `resource.cors.js`, which includes an Access-Control-Allow-Origin response header. + b. `resource.no-cors.js`, which doesn't include an Access-Control-Allow-Origin response header. + --> + <script> + promise_test(async () => { + const prefix = + "https://web-platform.test:8444/web-bundle/resources/wbn/no-cors/"; + const resources = [ + prefix + "resource.cors.js", + prefix + "resource.no-cors.js", + ]; + for (const crossorigin_attribute_value of [ + undefined, // crossorigin attribute is not set + "anonymous", + "use-credential", + ]) { + const link = await addLinkAndWaitForError( + prefix + "cross-origin.wbn", + resources, + crossorigin_attribute_value + ); + + // A subresource in the bundle can not be used in any case. + for (const resource of resources) { + await fetchAndWaitForReject(resource); + await addScriptAndWaitForError(resource); + } + link.remove(); + } + }, "Use CORS if crossorigin=anonymous or crossorigin=use-credential is specified. A cross origin bundle must not be loaded unless a server returns a valid Access-Control-Allow-Origin header."); + </script> +</body> diff --git a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cors.tentative.html b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cors.tentative.html new file mode 100644 index 00000000000..79201154a14 --- /dev/null +++ b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cors.tentative.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<title>Cross origin WebBundle subresource loading</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md" +/> +<link + rel="help" + href="https://html.spec.whatwg.org/multipage/#cors-settings-attribute" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <!-- + This wpt should run on an origin different from https://web-platform.test:8444/, + from where cross-orign WebBundles are served. + + This test uses a cross-origin WebBundle, + https://web-platform.test:8444/web-bundle/resources/wbn/cors/cross-origin.wbn, + which is served with an Access-Control-Allow-Origin response header. + + `cross-origin.wbn` includes two subresources: + a. `resource.cors.json`, which includes an Access-Control-Allow-Origin response header. + b. `resource.no-cors.json`, which doesn't include an Access-Control-Allow-Origin response header. + --> + <script> + promise_test(async () => { + const prefix = + "https://web-platform.test:8444/web-bundle/resources/wbn/cors/"; + const resources = [ + prefix + "resource.cors.js", + prefix + "resource.no-cors.js", + ]; + for (const crossorigin_attribute_value of [ + undefined, // crossorigin attribute is not set + "anonymous", + "use-credential", + ]) { + const link = await addLinkAndWaitForLoad( + prefix + "cross-origin.wbn", + resources, + crossorigin_attribute_value + ); + + // Can fetch a subresource which has a valid Access-Control-Allow-Origin response header. + const response = await fetch(prefix + "resource.cors.js"); + assert_true(response.ok); + const text = await response.text(); + assert_equals(text, "scriptLoaded('resource.cors.js');"); + + // Can not fetch a subresource which does NOT have a valid + // Access-Control-Allow-Origin response header. + await fetchAndWaitForReject(prefix + "resource.no-cors.js"); + + // Both subresource js can be loaded via a <script> element, which doesn't use cors. + for (const resource of resources) { + const scriptEvaluted = new Promise((resolve, reject) => { + window.scriptLoaded = resolve; + }); + const script = document.createElement("script"); + script.src = resource; + document.body.appendChild(script); + await scriptEvaluted; + } + link.remove(); + } + }, "request's mode must be cors. A server should return a valid Access-Control-Allow-Origin header if a bundle is a cross origin bundle."); + </script> +</body> diff --git a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-credential.tentative.sub.html b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-credential.tentative.sub.html new file mode 100644 index 00000000000..7b9fa7aee7a --- /dev/null +++ b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-credential.tentative.sub.html @@ -0,0 +1,98 @@ +<!DOCTYPE html> +<title> + crossorigin= attribute and credentials in WebBundle subresource loading +</title> +<link + rel="help" + href="https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md" +/> +<link + rel="help" + href="https://html.spec.whatwg.org/multipage/#cors-settings-attribute" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/test-helpers.js"></script> +<body> + <script> + // In this wpt, we only test request's credential mode, which controls + // whether UA sends a credential or not. + // We assume that a <link> element fires a load event correctly if + // check-cookie-and-return-bundle.py returns a valid format webbundle. That + // happens only when UA sends a credential. We don't care of the contents of + // a bundle. That's out of scope of this wpt. + + // See subresoruce-loading-cors{-error}.tentative.html, where we test subresource + // loading with crossorigin= attribute, in terms of request's mode (cors or no-cors). + + document.cookie = "milk=1"; + + // Make sure to set a cookie for a cross-origin domain from where a cross + // origin bundle is served. + const setCookiePromise = fetch( + "http://{{domains[www2]}}:{{ports[http][0]}}/cookies/resources/set-cookie.py?name=milk&path=/web-bundle/subresource-loading/", + { + mode: "no-cors", + credentials: "include", + } + ); + + const same_origin_bundle = "./check-cookie-and-return-bundle.py"; + const cross_origin_bundle = "http://{{domains[www2]}}:{{ports[http][0]}}/web-bundle/subresource-loading/check-cookie-and-return-bundle.py"; + + promise_test(async () => { + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = same_origin_bundle; + await addElementAndWaitForLoad(link); + link.remove() + }, "'no crossorigin attribute' should send a credential to a same origin bundle"); + + promise_test(async () => { + await setCookiePromise; + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = cross_origin_bundle; + await addElementAndWaitForError(link); + link.remove() + }, "'no crossorigin attribute' should not send a credential to a cross origin bundle"); + + promise_test(async () => { + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = same_origin_bundle; + link.crossOrigin = "anonymous"; + await addElementAndWaitForLoad(link); + link.remove() + }, "'anonymous' should send a credential to a same origin bundle"); + + promise_test(async () => { + await setCookiePromise; + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = cross_origin_bundle; + link.crossOrigin = "anonymous"; + await addElementAndWaitForError(link); + link.remove() + }, "'anonymous' should not send a credential to a cross origin bundle"); + + promise_test(async () => { + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = same_origin_bundle; + link.crossOrigin = "use-credentials"; + await addElementAndWaitForLoad(link); + link.remove() + }, "'use-credentials' should send a credential to a same origin bundle"); + + promise_test(async () => { + await setCookiePromise; + const link = document.createElement("link"); + link.rel = "webbundle"; + link.href = cross_origin_bundle; + link.crossOrigin = "use-credentials"; + await addElementAndWaitForLoad(link); + link.remove() + }, "'use-credentials' should send a credential to a cross origin bundle"); + </script> +</body> diff --git a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cross-origin.tentative.html b/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cross-origin.tentative.html deleted file mode 100644 index b0072ddb1f6..00000000000 --- a/tests/wpt/web-platform-tests/web-bundle/subresource-loading/subresource-loading-cross-origin.tentative.html +++ /dev/null @@ -1,93 +0,0 @@ -<!DOCTYPE html> -<title>Cross-origin WebBundle subresource loading</title> -<link - rel="help" - href="https://github.com/WICG/webpackage/blob/master/explainers/subresource-loading.md" -/> -<script src="/resources/testharness.js"></script> -<script src="/resources/testharnessreport.js"></script> -<body> - <!-- - This wpt should run on an origin different from https://web-platform.test:8444/, - from where cross-orign WebBundles are served. - - This test uses the two cross-origin WebBundles: - - 1. https://web-platform.test:8444/web-bundle/resources/wbn/cors/cross-origin.wbn, - which is served with an Access-Control-Allow-Origin response header. - 2. http://web-platform.test:8444/web-bundle/resources/wbn/no-cors/cross-origin.wbn, - which is served *without* an Access-Control-Allow-Origin response header. - - Each `cross-origin.wbn` includes two subresources: - a. `resource.cors.json`, which includes an Access-Control-Allow-Origin response header. - b. `resource.no-cors.json`, which doesn't include an Access-Control-Allow-Origin response header. - --> - <link - rel="webbundle" - href="https://web-platform.test:8444/web-bundle/resources/wbn/cors/cross-origin.wbn" - resources="https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.json - https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.json" - /> - <script> - promise_test(async () => { - const response = await fetch( - "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.cors.json" - ); - assert_true(response.ok); - const text = await response.text(); - assert_equals(text, "{ cors: 1 }"); - }, "A subresource which includes an Access-Control-Allow-Origin response header can be fetched"); - - promise_test(async (t) => { - return promise_rejects_js( - t, - TypeError, - fetch( - "https://web-platform.test:8444/web-bundle/resources/wbn/cors/resource.no-cors.json" - ) - ); - }, "A subresource which does not include an Access-Control-Allow-Origin response header can not be fetched"); - - promise_test(async (t) => { - const prefix = - "http://web-platform.test:8444/web-bundle/resources/wbn/no-cors/"; - const resources = [ - prefix + "resource.cors.json", - prefix + "resource.no-cors.json", - ] - // Should fire an error event on loading webbundle. - await addLinkAndWaitForError(prefix + "cross-origin.wbn", resources); - // A fetch should fail for any subresource specified in resources attribute. - for (const url of resources) { - await fetchAndWaitForReject(url); - } - }, "A cross-origin WebBundle which does not include an Access-Control-Allow-Origin response header should fire an error event on load, and a fetch should fail for any subresource"); - - function addLinkAndWaitForError(url, resources) { - return new Promise((resolve, reject) => { - const link = document.createElement("link"); - link.rel = "webbundle"; - link.href = url; - for (const resource of resources) { - link.resources.add(resource); - } - link.onload = reject; - link.onerror = () => resolve(link); - document.body.appendChild(link); - }); - } - - function fetchAndWaitForReject(url) { - return new Promise((resolve, reject) => { - fetch(url) - .then(() => { - reject(); - }) - .catch(() => { - resolve(); - }); - }); - } - - </script> -</body> diff --git a/tests/wpt/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/nan-param.html b/tests/wpt/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/nan-param.html new file mode 100644 index 00000000000..e9b8f0accbd --- /dev/null +++ b/tests/wpt/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/nan-param.html @@ -0,0 +1,92 @@ +<!doctype html> +<html> + <head> + <title>Test Flushing of NaN to Zero in AudioParams</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/webaudio/resources/audit-util.js"></script> + <script src="/webaudio/resources/audit.js"></script> + </head> + + <body> + <script> + let audit = Audit.createTaskRunner(); + + // See + // https://webaudio.github.io/web-audio-api/#computation-of-value. + // + // The computed value must replace NaN values in the output with + // the default value of the param. + audit.define('AudioParam NaN', async (task, should) => { + // For testing, we only need a small number of frames; and + // a low sample rate is perfectly fine. Use two channels. + // The first channel is for the AudioParam output. The + // second channel is for the AudioParam input. + let context = new OfflineAudioContext( + {numberOfChannels: 2, length: 256, sampleRate: 8192}); + let merger = new ChannelMergerNode( + context, {numberOfInputs: context.destination.channelCount}); + merger.connect(context.destination); + + // A constant source with a huge value. + let mod = new ConstantSourceNode(context, {offset: 1e30}); + + // Gain nodes with a huge positive gain and huge negative + // gain. Combined with the huge offset in |mod|, the + // output of the gain nodes are +Infinity and -Infinity. + let gainPos = new GainNode(context, {gain: 1e30}); + let gainNeg = new GainNode(context, {gain: -1e30}); + + mod.connect(gainPos); + mod.connect(gainNeg); + + // Connect these to the second merger channel. This is a + // sanity check that the AudioParam input really is NaN. + gainPos.connect(merger, 0, 1); + gainNeg.connect(merger, 0, 1); + + // Source whose AudioParam is connected to the graph + // that produces NaN values. Use a non-default value offset + // just in case something is wrong we get default for some + // other reason. + let src = new ConstantSourceNode(context, {offset: 100}); + + gainPos.connect(src.offset); + gainNeg.connect(src.offset); + + // AudioParam output goes to channel 1 of the destination. + src.connect(merger, 0, 0); + + // Let's go! + mod.start(); + src.start(); + + let buffer = await context.startRendering(); + + let input = buffer.getChannelData(1); + let output = buffer.getChannelData(0); + + // Have to test manually for NaN values in the input because + // NaN fails all comparisons. + let isNaN = true; + for (let k = 0; k < input.length; ++k) { + if (!Number.isNaN(input[k])) { + isNaN = false; + break; + } + } + + should(isNaN, 'AudioParam input contains only NaN').beTrue(); + + // Output of the AudioParam should have all NaN values + // replaced by the default. + should(output, 'AudioParam output') + .beConstantValueOf(src.offset.defaultValue); + + task.done(); + }); + + audit.run(); + </script> + </body> +</html> diff --git a/tests/wpt/web-platform-tests/webcodecs/audio-encoder.any.js b/tests/wpt/web-platform-tests/webcodecs/audio-encoder.any.js index 8b918dc0ba0..322a7d4077e 100644 --- a/tests/wpt/web-platform-tests/webcodecs/audio-encoder.any.js +++ b/tests/wpt/web-platform-tests/webcodecs/audio-encoder.any.js @@ -24,6 +24,44 @@ function make_audio_frame(timestamp, channels, sampleRate, length) { }); } +// Merge all audio buffers into a new big one with all the data. +function join_buffers(buffers) { + assert_greater_than_equal(buffers.length, 0); + let total_length = 0; + let base_buffer = buffers[0]; + for (const buffer of buffers) { + assert_not_equals(buffer, null); + assert_equals(buffer.sampleRate, base_buffer.sampleRate); + assert_equals(buffer.numberOfChannels, base_buffer.numberOfChannels); + total_length += buffer.length; + } + + let result = new AudioBuffer({ + length: total_length, + numberOfChannels: base_buffer.numberOfChannels, + sampleRate: base_buffer.sampleRate + }); + + for (let i = 0; i < base_buffer.numberOfChannels; i++) { + let channel = result.getChannelData(i); + let position = 0; + for (const buffer of buffers) { + channel.set(buffer.getChannelData(i), position); + position += buffer.length; + } + assert_equals(position, total_length); + } + + return result; +} + +function clone_frame(frame) { + return new AudioFrame({ + timestamp: frame.timestamp, + buffer: join_buffers([frame.buffer]) + }); +} + promise_test(async t => { let sample_rate = 48000; let total_duration_s = 2; @@ -55,7 +93,7 @@ promise_test(async t => { let frame_duration_s = total_duration_s / frame_count; let length = frame_duration_s * config.sampleRate; let frame = make_audio_frame(timestamp_us, config.numberOfChannels, - config.sampleRate, length); + config.sampleRate, length); encoder.encode(frame); timestamp_us += frame_duration_s * 1_000_000; } @@ -68,3 +106,139 @@ promise_test(async t => { assert_greater_than(timestamp_us, chunk.timestamp); } }, 'Simple audio encoding'); + + +promise_test(async t => { + let sample_rate = 48000; + let total_duration_s = 2; + let frame_count = 20; + let input_frames = []; + let output_frames = []; + + let decoder_init = { + error: t.unreached_func("Decode error"), + output: frame => { + output_frames.push(frame); + } + }; + let decoder = new AudioDecoder(decoder_init); + + let encoder_init = { + error: t.unreached_func("Encoder error"), + output: (chunk, config) => { + if (config) + decoder.configure(config); + decoder.decode(chunk); + } + }; + let encoder = new AudioEncoder(encoder_init); + + let config = { + codec: 'opus', + sampleRate: sample_rate, + numberOfChannels: 2, + bitrate: 256000, //256kbit + }; + encoder.configure(config); + + let timestamp_us = 0; + const frame_duration_s = total_duration_s / frame_count; + const frame_length = frame_duration_s * config.sampleRate; + for (let i = 0; i < frame_count; i++) { + let frame = make_audio_frame(timestamp_us, config.numberOfChannels, + config.sampleRate, frame_length); + input_frames.push(clone_frame(frame)); + encoder.encode(frame); + timestamp_us += frame_duration_s * 1_000_000; + } + await encoder.flush(); + encoder.close(); + await decoder.flush(); + decoder.close(); + + + let total_input = join_buffers(input_frames.map(f => f.buffer)); + let total_output = join_buffers(output_frames.map(f => f.buffer)); + assert_equals(total_output.numberOfChannels, 2); + assert_equals(total_output.sampleRate, sample_rate); + + // Output can be slightly longer that the input due to padding + assert_greater_than_equal(total_output.length, total_input.length); + assert_greater_than_equal(total_output.duration, total_duration_s); + assert_approx_equals(total_output.duration, total_duration_s, 0.1); + + // Compare waveform before and after encoding + for (let channel = 0; channel < total_input.numberOfChannels; channel++) { + let input_data = total_input.getChannelData(channel); + let output_data = total_output.getChannelData(channel); + for (let i = 0; i < total_input.length; i++) { + assert_approx_equals(input_data[i], output_data[i], 0.5, + "Difference between input and output is too large." + + " index: " + i + + " input: " + input_data[i] + + " output: " + output_data[i]); + } + } + +}, 'Encoding and decoding'); + +promise_test(async t => { + let output_count = 0; + let encoder_config = { + codec: 'opus', + sampleRate: 24000, + numberOfChannels: 1, + bitrate: 96000 + }; + let decoder_config = null; + + let init = { + error: t.unreached_func("Encoder error"), + output: (chunk, config) => { + // Only the first invocation of the output callback is supposed to have + // a |config| in it. + output_count++; + if (output_count == 1) { + assert_not_equals(config, null); + decoder_config = config; + } else { + assert_equals(config, null); + } + } + }; + + let encoder = new AudioEncoder(init); + encoder.configure(encoder_config); + + let long_frame = make_audio_frame(0, encoder_config.numberOfChannels, + encoder_config.sampleRate, encoder_config.sampleRate); + encoder.encode(clone_frame(long_frame)); + await encoder.flush(); + + // Long frame produced more than one output, and we've got decoder_config + assert_greater_than(output_count, 1); + assert_not_equals(decoder_config, null); + assert_equals(decoder_config.codec, encoder_config.codec); + assert_equals(decoder_config.sampleRate, encoder_config.sampleRate); + assert_equals(decoder_config.numberOfChannels, encoder_config.numberOfChannels); + + // Check that description start with 'Opus' + let extra_data = new Uint8Array(decoder_config.description); + assert_equals(extra_data[0], 0x4f); + assert_equals(extra_data[1], 0x70); + assert_equals(extra_data[2], 0x75); + assert_equals(extra_data[3], 0x73); + + decoder_config = null; + output_count = 0; + encoder_config.bitrate = 256000; + encoder.configure(encoder_config); + encoder.encode(clone_frame(long_frame)); + await encoder.flush(); + + // After reconfiguring encoder should produce decoder config again + assert_greater_than(output_count, 1); + assert_not_equals(decoder_config, null); + assert_not_equals(decoder_config.description, null); + encoder.close(); +}, "Emit decoder config and extra data.");
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/webcodecs/utils.js b/tests/wpt/web-platform-tests/webcodecs/utils.js index 9b0a022af8e..168d3722048 100644 --- a/tests/wpt/web-platform-tests/webcodecs/utils.js +++ b/tests/wpt/web-platform-tests/webcodecs/utils.js @@ -140,3 +140,46 @@ function verifyPlane(expected, actual) { testBuffer.slice(h * actual.stride, expected.stride), 'plane data'); } } + +// Reference values generated by: +// https://fiddle.skia.org/c/f100d4d5f085a9e09896aabcbc463868 + +const kSRGBPixel = [50, 100, 150, 255]; +const kP3Pixel = [62, 99, 146, 255]; +const kRec2020Pixel = [87, 106, 151, 255]; + +const kCanvasOptionsP3Uint8 = { + colorSpace: 'display-p3', + pixelFormat: 'uint8' +}; + +const kImageSettingOptionsP3Uint8 = { + colorSpace: 'display-p3', + storageFormat: 'uint8' +}; + +const kCanvasOptionsRec2020Uint8 = { + colorSpace: 'rec2020', + pixelFormat: 'uint8' +}; + +const kImageSettingOptionsRec2020Uint8 = { + colorSpace: 'rec2020', + storageFormat: 'uint8' +}; + +function testCanvas(ctx, width, height, expected_pixel, imageSetting, assert_compares) { + // The dup getImageData is to workaournd crbug.com/1100233 + let imageData = ctx.getImageData(0, 0, width, height, imageSetting); + let colorData = ctx.getImageData(0, 0, width, height, imageSetting).data; + const kMaxPixelToCheck = 128 * 96; + let step = width * height / kMaxPixelToCheck; + step = Math.round(step); + step = (step < 1) ? 1 : step; + for (let i = 0; i < 4 * width * height; i += (4 * step)) { + assert_compares(colorData[i], expected_pixel[0]); + assert_compares(colorData[i + 1], expected_pixel[1]); + assert_compares(colorData[i + 2], expected_pixel[2]); + assert_compares(colorData[i + 3], expected_pixel[3]); + } +} diff --git a/tests/wpt/web-platform-tests/webcodecs/video-frame-serialization.any.js b/tests/wpt/web-platform-tests/webcodecs/video-frame-serialization.any.js index 338f721da8f..a0b28a299fb 100644 --- a/tests/wpt/web-platform-tests/webcodecs/video-frame-serialization.any.js +++ b/tests/wpt/web-platform-tests/webcodecs/video-frame-serialization.any.js @@ -67,34 +67,13 @@ async_test(t => { }) localPort.onmessage = t.step_func_done((e) => { - assert_not_equals(localFrame.timestamp, defaultInit.timestamp); - }) - - localPort.postMessage(localFrame); - -}, 'Verify closing frames propagates accross contexts.'); - -async_test(t => { - let localFrame = createDefaultVideoFrame(); - - let channel = new MessageChannel(); - let localPort = channel.port1; - let externalPort = channel.port2; - - externalPort.onmessage = t.step_func((e) => { - let externalFrame = e.data; - externalFrame.close(); - externalPort.postMessage("Done"); - }) - - localPort.onmessage = t.step_func_done((e) => { assert_equals(localFrame.timestamp, defaultInit.timestamp); localFrame.close(); }) - localPort.postMessage(localFrame.clone()); + localPort.postMessage(localFrame); -}, 'Verify closing cloned frames doesn\'t propagate accross contexts.'); +}, 'Verify closing frames does not propagate accross contexts.'); async_test(t => { let localFrame = createDefaultVideoFrame(); diff --git a/tests/wpt/web-platform-tests/webcodecs/video-frame.any.js b/tests/wpt/web-platform-tests/webcodecs/video-frame.any.js index b02badc082f..3ae10f829a0 100644 --- a/tests/wpt/web-platform-tests/webcodecs/video-frame.any.js +++ b/tests/wpt/web-platform-tests/webcodecs/video-frame.any.js @@ -311,7 +311,7 @@ test(t => { let yPlane = {src: yPlaneData, stride: 4, rows: 2}; let uvPlaneData = new Uint8Array([1, 2, 3, 4]); let uvPlane = {src: uvPlaneData, stride: 4, rows: 1}; - frame = new VideoFrame(fmt, [yPlane, uvPlane], vfInit); + let frame = new VideoFrame(fmt, [yPlane, uvPlane], vfInit); assert_equals(frame.planes.length, 2, 'plane count'); assert_equals(frame.format, fmt, 'plane format'); verifyPlane(yPlane, frame.planes[0]); @@ -368,7 +368,7 @@ test(t => { let argbPlaneData = new Uint8Array(new Uint32Array([1, 2, 3, 4, 5, 6, 7, 8]).buffer); let argbPlane = {src: argbPlaneData, stride: 4 * 4, rows: 2}; - frame = new VideoFrame('ABGR', [argbPlane], vfInit); + let frame = new VideoFrame('ABGR', [argbPlane], vfInit); assert_equals(frame.planes.length, 1, 'plane count'); assert_equals(frame.format, 'ABGR', 'plane format'); verifyPlane(argbPlane, frame.planes[0]); diff --git a/tests/wpt/web-platform-tests/webcodecs/videoFrame-createImageBitmap.any.js b/tests/wpt/web-platform-tests/webcodecs/videoFrame-createImageBitmap.any.js new file mode 100644 index 00000000000..7b08d292084 --- /dev/null +++ b/tests/wpt/web-platform-tests/webcodecs/videoFrame-createImageBitmap.any.js @@ -0,0 +1,146 @@ +// META: global=window,dedicatedworker +// META: script=/webcodecs/utils.js + +function testImageBitmapToAndFromVideoFrame(width, height, expectedPixel, + canvasOptions, imageBitmapOptions, imageSetting) { + let canvas = new OffscreenCanvas(width, height); + let ctx = canvas.getContext('2d', canvasOptions); + ctx.fillStyle = 'rgb(50, 100, 150)'; + ctx.fillRect(0, 0, width, height); + testCanvas(ctx, width, height, expectedPixel, imageSetting, assert_equals); + + return createImageBitmap(canvas, imageBitmapOptions) + .then((fromImageBitmap) => { + let videoFrame = new VideoFrame(fromImageBitmap, { + timestamp: 0 + }); + return createImageBitmap(videoFrame, imageBitmapOptions); + }) + .then((toImageBitmap) => { + let myCanvas = new OffscreenCanvas(width, height); + let myCtx = myCanvas.getContext('2d', canvasOptions); + myCtx.drawImage(toImageBitmap, 0, 0); + let tolerance = 2; + testCanvas(myCtx, width, height, expectedPixel, imageSetting, (actual, expected) => { + assert_approx_equals(actual, expected, tolerance); + }); + }); +} + +promise_test(() => { + return testImageBitmapToAndFromVideoFrame(48, 36, kSRGBPixel); +}, 'ImageBitmap<->VideoFrame with canvas(48x36 srgb uint8).'); + +promise_test(() => { + return testImageBitmapToAndFromVideoFrame(480, 360, kSRGBPixel); +}, 'ImageBitmap<->VideoFrame with canvas(480x360 srgb uint8).'); + +promise_test(() => { + return testImageBitmapToAndFromVideoFrame(48, 36, kP3Pixel, + kCanvasOptionsP3Uint8, { + colorSpaceConversion: "none" + }, kImageSettingOptionsP3Uint8); +}, 'ImageBitmap<->VideoFrame with canvas(48x36 display-p3 uint8).'); + +promise_test(() => { + return testImageBitmapToAndFromVideoFrame(480, 360, kP3Pixel, + kCanvasOptionsP3Uint8, { + colorSpaceConversion: "none" + }, kImageSettingOptionsP3Uint8); +}, 'ImageBitmap<->VideoFrame with canvas(480x360 display-p3 uint8).'); + +promise_test(() => { + return testImageBitmapToAndFromVideoFrame(48, 36, kRec2020Pixel, + kCanvasOptionsRec2020Uint8, { + colorSpaceConversion: "none" + }, kImageSettingOptionsRec2020Uint8); +}, 'ImageBitmap<->VideoFrame with canvas(48x36 rec2020 uint8).'); + +promise_test(() => { + return testImageBitmapToAndFromVideoFrame(480, 360, kRec2020Pixel, + kCanvasOptionsRec2020Uint8, { + colorSpaceConversion: "none" + }, kImageSettingOptionsRec2020Uint8); +}, 'ImageBitmap<->VideoFrame with canvas(480x360 rec2020 uint8).'); + +function testCreateImageBitmapFromVideoFrameVP9Decoder() { + // Prefers hardware decoders by setting video size as large as 720p. + const width = 1280; + const height = 720; + + let canvas = new OffscreenCanvas(width, height); + let ctx = canvas.getContext('2d'); + ctx.fillStyle = 'rgb(50, 100, 150)'; + ctx.fillRect(0, 0, width, height); + + return createImageBitmap(canvas).then((fromImageBitmap) => { + let videoFrame = new VideoFrame(fromImageBitmap, { + timestamp: 0 + }); + return new Promise((resolve, reject) => { + let processVideoFrame = (frame) => { + createImageBitmap(frame).then((toImageBitmap) => { + let myCanvas = new OffscreenCanvas(width, height); + let myCtx = myCanvas.getContext('2d'); + myCtx.drawImage(toImageBitmap, 0, 0); + let tolerance = 6; + try { + testCanvas(myCtx, width, height, kSRGBPixel, null, + (actual, expected) => { + assert_approx_equals(actual, expected, tolerance); + } + ); + } catch (error) { + reject(error); + } + resolve('Done.'); + }); + }; + + const decoderInit = { + output: processVideoFrame, + error: (e) => { + reject(e); + } + }; + + const encodedVideoConfig = { + codec: "vp09.00.10.08", + }; + + let decoder = new VideoDecoder(decoderInit); + decoder.configure(encodedVideoConfig); + + let processVideoChunk = (chunk) => { + decoder.decode(chunk); + decoder.flush(); + }; + + const encoderInit = { + output: processVideoChunk, + error: (e) => { + reject(e); + } + }; + + const videoEncoderConfig = { + codec: "vp09.00.10.08", + width: width, + height: height, + bitrate: 10e6, + framerate: 30, + }; + + let encoder = new VideoEncoder(encoderInit); + encoder.configure(videoEncoderConfig); + encoder.encode(videoFrame, { + keyFrame: true + }); + encoder.flush(); + }); + }); +} + +promise_test(() => { + return testCreateImageBitmapFromVideoFrameVP9Decoder(); +}, 'Create ImageBitmap for a VideoFrame from VP9 decoder.'); diff --git a/tests/wpt/web-platform-tests/webcodecs/videoFrame-drawImage.any.js b/tests/wpt/web-platform-tests/webcodecs/videoFrame-drawImage.any.js new file mode 100644 index 00000000000..bac4c890190 --- /dev/null +++ b/tests/wpt/web-platform-tests/webcodecs/videoFrame-drawImage.any.js @@ -0,0 +1,54 @@ +// META: global=window,dedicatedworker +// META: script=/webcodecs/utils.js + +function testDrawImageFromVideoFrame( + width, height, expectedPixel, canvasOptions, imageBitmapOptions, + imageSetting) { + let vfInit = {timestamp: 0, codedWidth: width, codedHeight: height}; + let u32_data = new Uint32Array(vfInit.codedWidth * vfInit.codedHeight); + u32_data.fill(0xFF966432); // 'rgb(50, 100, 150)'; + let argbPlaneData = new Uint8Array(u32_data.buffer); + let argbPlane = { + src: argbPlaneData, + stride: width * 4, + rows: height + }; + let frame = new VideoFrame('ABGR', [argbPlane], vfInit); + let canvas = new OffscreenCanvas(width, height); + let ctx = canvas.getContext('2d', canvasOptions); + ctx.drawImage(frame, 0, 0); + testCanvas(ctx, width, height, expectedPixel, imageSetting, assert_equals); + frame.close(); +} + +test(() => { + return testDrawImageFromVideoFrame(48, 36, kSRGBPixel); +}, 'drawImage(VideoFrame) with canvas(48x36 srgb uint8).'); + +test(() => { + return testDrawImageFromVideoFrame(480, 360, kSRGBPixel); +}, 'drawImage(VideoFrame) with canvas(480x360 srgb uint8).'); + +test(() => { + return testDrawImageFromVideoFrame( + 48, 36, kP3Pixel, kCanvasOptionsP3Uint8, {colorSpaceConversion: 'none'}, + kImageSettingOptionsP3Uint8); +}, 'drawImage(VideoFrame) with canvas(48x36 display-p3 uint8).'); + +test(() => { + return testDrawImageFromVideoFrame( + 480, 360, kP3Pixel, kCanvasOptionsP3Uint8, {colorSpaceConversion: 'none'}, + kImageSettingOptionsP3Uint8); +}, 'drawImage(VideoFrame) with canvas(480x360 display-p3 uint8).'); + +test(() => { + return testDrawImageFromVideoFrame( + 48, 36, kRec2020Pixel, kCanvasOptionsRec2020Uint8, + {colorSpaceConversion: 'none'}, kImageSettingOptionsRec2020Uint8); +}, 'drawImage(VideoFrame) with canvas(48x36 rec2020 uint8).'); + +test(() => { + return testDrawImageFromVideoFrame( + 480, 360, kRec2020Pixel, kCanvasOptionsRec2020Uint8, + {colorSpaceConversion: 'none'}, kImageSettingOptionsRec2020Uint8); +}, 'drawImage(VideoFrame) with canvas(480x360 rec2020 uint8).'); diff --git a/tests/wpt/web-platform-tests/webcodecs/videoFrame-texImage.any.js b/tests/wpt/web-platform-tests/webcodecs/videoFrame-texImage.any.js new file mode 100644 index 00000000000..4bb8ce1e5ed --- /dev/null +++ b/tests/wpt/web-platform-tests/webcodecs/videoFrame-texImage.any.js @@ -0,0 +1,100 @@ +// META: global=window,dedicatedworker +// META: script=/webcodecs/utils.js +// META: script=/webcodecs/webgl-test-utils.js + +function testGLCanvas(gl, width, height, expectedPixel, assertCompares) { + var colorData = + new Uint8Array(gl.drawingBufferWidth * gl.drawingBufferHeight * 4); + gl.readPixels( + 0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, + gl.UNSIGNED_BYTE, colorData); + assertCompares(gl.getError(), gl.NO_ERROR); + + const kMaxPixelToCheck = 128 * 96; + let step = width * height / kMaxPixelToCheck; + step = Math.round(step); + step = (step < 1) ? 1 : step; + for (let i = 0; i < 4 * width * height; i += (4 * step)) { + assertCompares(colorData[i], expectedPixel[0]); + assertCompares(colorData[i + 1], expectedPixel[1]); + assertCompares(colorData[i + 2], expectedPixel[2]); + assertCompares(colorData[i + 3], expectedPixel[3]); + } +} + +function testTexImage2DFromVideoFrame( + width, height, useTexSubImage2D, expectedPixel) { + let vfInit = {timestamp: 0, codedWidth: width, codedHeight: height}; + let u32Data = new Uint32Array(vfInit.codedWidth * vfInit.codedHeight); + u32Data.fill(0xFF966432); // 'rgb(50, 100, 150)'; + let argbPlaneData = new Uint8Array(u32Data.buffer); + let argbPlane = {src: argbPlaneData, stride: width * 4, rows: height}; + let frame = new VideoFrame('ABGR', [argbPlane], vfInit); + + let gl_canvas = new OffscreenCanvas(width, height); + let gl = gl_canvas.getContext('webgl'); + + let program = WebGLTestUtils.setupTexturedQuad(gl); + gl.clearColor(0, 0, 0, 1); + gl.clearDepth(1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.colorMask(1, 1, 1, 0); // Disable any writes to the alpha channel. + let textureLoc = gl.getUniformLocation(program, 'tex'); + + let texture = gl.createTexture(); + + // Bind the texture to texture unit 0. + gl.bindTexture(gl.TEXTURE_2D, texture); + + // Set up texture parameters. + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + // Set up pixel store parameters. + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + + // Upload the videoElement into the texture + if (useTexSubImage2D) { + // Initialize the texture to black first + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, + null); + gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, frame); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, frame); + } + + frame.close(); + + assert_equals(gl.getError(), gl.NO_ERROR); + + // Point the uniform sampler to texture unit 0 + gl.uniform1i(textureLoc, 0); + + // Draw the triangles + WebGLTestUtils.drawQuad(gl, [0, 0, 0, 255]); + + // Wait for drawing to complete. + gl.finish(); + + testGLCanvas(gl, width, height, expectedPixel, assert_equals); +} + +test(() => { + return testTexImage2DFromVideoFrame(48, 36, false, kSRGBPixel); +}, 'drawImage(VideoFrame) with texImage2D (48x36) srgb.'); + +test(() => { + return testTexImage2DFromVideoFrame(48, 36, true, kSRGBPixel); +}, 'drawImage(VideoFrame) with texSubImage2D (48x36) srgb.'); + +test(() => { + return testTexImage2DFromVideoFrame(480, 360, false, kSRGBPixel); +}, 'drawImage(VideoFrame) with texImage2D (480x360) srgb.'); + +test(() => { + return testTexImage2DFromVideoFrame(480, 360, true, kSRGBPixel); +}, 'drawImage(VideoFrame) with texSubImage2D (480x360) srgb.'); diff --git a/tests/wpt/web-platform-tests/webcodecs/webgl-test-utils.js b/tests/wpt/web-platform-tests/webcodecs/webgl-test-utils.js new file mode 100644 index 00000000000..847fe0351f7 --- /dev/null +++ b/tests/wpt/web-platform-tests/webcodecs/webgl-test-utils.js @@ -0,0 +1,321 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +WebGLTestUtils = (function() { + /** + * Converts a WebGL enum to a string + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {number} value The enum value. + * @return {string} The enum as a string. + */ + var glEnumToString = function(gl, value) { + for (var p in gl) { + if (gl[p] == value) { + return p; + } + } + return '0x' + value.toString(16); + }; + + var lastError = ''; + + /** + * Returns the last compiler/linker error. + * @return {string} The last compiler/linker error. + */ + var getLastError = function() { + return lastError; + }; + + // clang-format off + + /** + * A vertex shader for a single texture. + * @type {string} + */ + var simpleTextureVertexShader = [ + 'attribute vec4 vPosition;', // + 'attribute vec2 texCoord0;', + 'varying vec2 texCoord;', + 'void main() {', + ' gl_Position = vPosition;', + ' texCoord = texCoord0;', + '}' + ].join('\n'); + + /** + * A fragment shader for a single texture. + * @type {string} + */ + var simpleTextureFragmentShader = [ + 'precision mediump float;', + 'uniform sampler2D tex;', + 'varying vec2 texCoord;', + 'void main() {', + ' gl_FragData[0] = texture2D(tex, texCoord);', + '}' + ].join('\n'); + + // clang-format on + + /** + * Creates a simple texture vertex shader. + * @param {!WebGLContext} gl The WebGLContext to use. + * @return {!WebGLShader} + */ + var setupSimpleTextureVertexShader = function(gl) { + return loadShader(gl, simpleTextureVertexShader, gl.VERTEX_SHADER); + }; + + /** + * Creates a simple texture fragment shader. + * @param {!WebGLContext} gl The WebGLContext to use. + * @return {!WebGLShader} + */ + var setupSimpleTextureFragmentShader = function(gl) { + return loadShader(gl, simpleTextureFragmentShader, gl.FRAGMENT_SHADER); + }; + + /** + * Creates a program, attaches shaders, binds attrib locations, links the + * program and calls useProgram. + * @param {!Array.<!WebGLShader>} shaders The shaders to attach . + * @param {!Array.<string>} opt_attribs The attribs names. + * @param {!Array.<number>} opt_locations The locations for the attribs. + */ + var setupProgram = function(gl, shaders, opt_attribs, opt_locations) { + var realShaders = []; + var program = gl.createProgram(); + for (var ii = 0; ii < shaders.length; ++ii) { + var shader = shaders[ii]; + if (typeof shader == 'string') { + var element = document.getElementById(shader); + if (element) { + shader = loadShaderFromScript(gl, shader); + } else { + shader = loadShader( + gl, shader, ii ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER); + } + } + gl.attachShader(program, shader); + } + if (opt_attribs) { + for (var ii = 0; ii < opt_attribs.length; ++ii) { + gl.bindAttribLocation( + program, opt_locations ? opt_locations[ii] : ii, opt_attribs[ii]); + } + } + gl.linkProgram(program); + + // Check the link status + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + gl.deleteProgram(program); + return null; + } + + gl.useProgram(program); + return program; + }; + + /** + * Creates a simple texture program. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {number} opt_positionLocation The attrib location for position. + * @param {number} opt_texcoordLocation The attrib location for texture + * coords. + * @return {WebGLProgram} + */ + var setupSimpleTextureProgram = function( + gl, opt_positionLocation, opt_texcoordLocation) { + opt_positionLocation = opt_positionLocation || 0; + opt_texcoordLocation = opt_texcoordLocation || 1; + var vs = setupSimpleTextureVertexShader(gl); + var fs = setupSimpleTextureFragmentShader(gl); + if (!vs || !fs) { + return null; + } + var program = setupProgram( + gl, [vs, fs], ['vPosition', 'texCoord0'], + [opt_positionLocation, opt_texcoordLocation]); + if (!program) { + gl.deleteShader(fs); + gl.deleteShader(vs); + } + gl.useProgram(program); + return program; + }; + + /** + * Creates buffers for a textured unit quad and attaches them to vertex + * attribs. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {number} opt_positionLocation The attrib location for position. + * @param {number} opt_texcoordLocation The attrib location for texture + * coords. + * @return {!Array.<WebGLBuffer>} The buffer objects that were + * created. + */ + var setupUnitQuad = function(gl, opt_positionLocation, opt_texcoordLocation) { + opt_positionLocation = opt_positionLocation || 0; + opt_texcoordLocation = opt_texcoordLocation || 1; + var objects = []; + + var vertexObject = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); + gl.bufferData( + gl.ARRAY_BUFFER, new Float32Array([ + 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, -1.0, + -1.0, 0.0, 1.0, -1.0, 0.0 + ]), + gl.STATIC_DRAW); + gl.enableVertexAttribArray(opt_positionLocation); + gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0); + objects.push(vertexObject); + + var vertexObject = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array( + [1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0]), + gl.STATIC_DRAW); + gl.enableVertexAttribArray(opt_texcoordLocation); + gl.vertexAttribPointer(opt_texcoordLocation, 2, gl.FLOAT, false, 0, 0); + objects.push(vertexObject); + return objects; + }; + + /** + * Creates a program and buffers for rendering a textured quad. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {number} opt_positionLocation The attrib location for position. + * @param {number} opt_texcoordLocation The attrib location for texture + * coords. + * @return {!WebGLProgram} + */ + var setupTexturedQuad = function( + gl, opt_positionLocation, opt_texcoordLocation) { + var program = setupSimpleTextureProgram( + gl, opt_positionLocation, opt_texcoordLocation); + setupUnitQuad(gl, opt_positionLocation, opt_texcoordLocation); + return program; + }; + + /** + * Draws a previously setup quad. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {!Array.<number>} opt_color The color to fill clear with before + * drawing. A 4 element array where each element is in the range 0 to + * 255. Default [255, 255, 255, 255] + */ + var drawQuad = function(gl, opt_color) { + opt_color = opt_color || [255, 255, 255, 255]; + gl.clearColor( + opt_color[0] / 255, opt_color[1] / 255, opt_color[2] / 255, + opt_color[3] / 255); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.drawArrays(gl.TRIANGLES, 0, 6); + }; + + /** + * Links a WebGL program, throws if there are errors. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {!WebGLProgram} program The WebGLProgram to link. + * @param {function(string): void) opt_errorCallback callback for errors. + */ + var linkProgram = function(gl, program, opt_errorCallback) { + // Link the program + gl.linkProgram(program); + + // Check the link status + var linked = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!linked) { + // something went wrong with the link + gl.deleteProgram(program); + return false; + } + + return true; + }; + + /** + * Loads a shader. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {string} shaderSource The shader source. + * @param {number} shaderType The type of shader. + * @param {function(string): void) opt_errorCallback callback for errors. + * @return {!WebGLShader} The created shader. + */ + var loadShader = + function(gl, shaderSource, shaderType, opt_errorCallback) { + var errFn = opt_errorCallback || (_ => {}); + // Create the shader object + var shader = gl.createShader(shaderType); + if (shader == null) { + errFn('*** Error: unable to create shader \'' + shaderSource + '\''); + return null; + } + + // Load the shader source + gl.shaderSource(shader, shaderSource); + var err = gl.getError(); + if (err != gl.NO_ERROR) { + errFn( + '*** Error loading shader \'' + shader + + '\':' + glEnumToString(gl, err)); + return null; + } + + // Compile the shader + gl.compileShader(shader); + + // Check the compile status + var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!compiled) { + // Something went wrong during compilation; get the error + lastError = gl.getShaderInfoLog(shader); + errFn('*** Error compiling shader \'' + shader + '\':' + lastError); + gl.deleteShader(shader); + return null; + } + + return shader; + } + + /** + * Loads shaders from source, creates a program, attaches the shaders and + * links. + * @param {!WebGLContext} gl The WebGLContext to use. + * @param {string} vertexShader The vertex shader. + * @param {string} fragmentShader The fragment shader. + * @param {function(string): void) opt_errorCallback callback for errors. + * @return {!WebGLProgram} The created program. + */ + var loadProgram = function( + gl, vertexShader, fragmentShader, opt_errorCallback) { + var program = gl.createProgram(); + gl.attachShader( + program, + loadShader(gl, vertexShader, gl.VERTEX_SHADER, opt_errorCallback)); + gl.attachShader( + program, + loadShader(gl, fragmentShader, gl.FRAGMENT_SHADER, opt_errorCallback)); + return linkProgram(gl, program, opt_errorCallback) ? program : null; + }; + + return { + drawQuad: drawQuad, + getLastError: getLastError, + glEnumToString: glEnumToString, + loadProgram: loadProgram, + loadShader: loadShader, + setupProgram: setupProgram, + setupSimpleTextureFragmentShader: setupSimpleTextureFragmentShader, + setupSimpleTextureProgram: setupSimpleTextureProgram, + setupSimpleTextureVertexShader: setupSimpleTextureVertexShader, + setupTexturedQuad: setupTexturedQuad, + setupUnitQuad: setupUnitQuad, + }; +}()); diff --git a/tests/wpt/web-platform-tests/webdriver/tests/bidi/new_session/__init__.py b/tests/wpt/web-platform-tests/webdriver/tests/bidi/new_session/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/tests/wpt/web-platform-tests/webdriver/tests/bidi/new_session/__init__.py diff --git a/tests/wpt/web-platform-tests/webdriver/tests/bidi/new_session/connect.py b/tests/wpt/web-platform-tests/webdriver/tests/bidi/new_session/connect.py new file mode 100644 index 00000000000..283853562df --- /dev/null +++ b/tests/wpt/web-platform-tests/webdriver/tests/bidi/new_session/connect.py @@ -0,0 +1,43 @@ +import pytest +import asyncio +import websockets + +# classic session to enable bidi capability manually +@pytest.mark.asyncio +@pytest.mark.capabilities({"webSocketUrl": True}) +async def test_websocket_url_connect(session): + websocket_url = session.capabilities["webSocketUrl"] + async with websockets.connect(websocket_url) as websocket: + await websocket.send("Hello world!") + await websocket.close() + +# bidi session following classic session to test session +# recreation with bidi true in session fixture. +# Close websocket at the end. +@pytest.mark.asyncio +@pytest.mark.bidi(True) +async def test_bidi_session_1(session): + await session.websocket_transport.send("test_bidi_session_1") + await session.websocket_transport.close() + +# bidi session following a bidi session with the same capabilities +# but closed websocket to test restart of websocket connection. +@pytest.mark.asyncio +@pytest.mark.bidi(True) +async def test_bidi_session_2(session): + await session.websocket_transport.send("test_bidi_session_2") + await session.websocket_transport.close() + +# bidi session following a bidi session with a different capabilities +# to test session recreation +@pytest.mark.asyncio +@pytest.mark.bidi(True) +@pytest.mark.capabilities({"acceptInsecureCerts": True}) +async def test_bidi_session_3(session): + await session.websocket_transport.send("test_bidi_session_3") + +# classic session following a bidi session to test session +# recreation +@pytest.mark.asyncio +async def test_classic(session): + pass diff --git a/tests/wpt/web-platform-tests/webdriver/tests/execute_async_script/execute_async.py b/tests/wpt/web-platform-tests/webdriver/tests/execute_async_script/execute_async.py index c769ca811ee..5746ed6f05f 100644 --- a/tests/wpt/web-platform-tests/webdriver/tests/execute_async_script/execute_async.py +++ b/tests/wpt/web-platform-tests/webdriver/tests/execute_async_script/execute_async.py @@ -1,8 +1,10 @@ import pytest +from webdriver.error import NoSuchAlertException from webdriver.transport import Response from tests.support.asserts import assert_error, assert_success +from tests.support.sync import Poll def execute_async_script(session, script, args=None): @@ -50,9 +52,16 @@ def test_abort_by_user_prompt_twice(session, dialog_type): session.alert.accept() - # The first alert has been accepted by the user prompt handler, the second one remains. - # FIXME: this is how browsers currently work, but the spec should clarify if this is the - # expected behavior, see https://github.com/w3c/webdriver/issues/1153. - assert session.alert.text == "Bye" + # The first alert has been accepted by the user prompt handler, the second + # alert will still be opened because the current step isn't aborted. + wait = Poll( + session, + timeout=5, + message="Second alert has not been opened", + ignored_exceptions=NoSuchAlertException + ) + text = wait.until(lambda s: s.alert.text) + + assert text == "Bye" session.alert.accept() diff --git a/tests/wpt/web-platform-tests/webdriver/tests/execute_script/execute.py b/tests/wpt/web-platform-tests/webdriver/tests/execute_script/execute.py index 1d1511be376..0f934cbb445 100644 --- a/tests/wpt/web-platform-tests/webdriver/tests/execute_script/execute.py +++ b/tests/wpt/web-platform-tests/webdriver/tests/execute_script/execute.py @@ -1,5 +1,6 @@ import pytest +from webdriver.error import NoSuchAlertException from webdriver.transport import Response from tests.support.asserts import assert_error, assert_success @@ -87,9 +88,16 @@ def test_abort_by_user_prompt_twice(session, dialog_type): session.alert.accept() - # The first alert has been accepted by the user prompt handler, the second one remains. - # FIXME: this is how browsers currently work, but the spec should clarify if this is the - # expected behavior, see https://github.com/w3c/webdriver/issues/1153. - assert session.alert.text == "Bye" + # The first alert has been accepted by the user prompt handler, the second + # alert will still be opened because the current step isn't aborted. + wait = Poll( + session, + timeout=5, + message="Second alert has not been opened", + ignored_exceptions=NoSuchAlertException + ) + text = wait.until(lambda s: s.alert.text) + + assert text == "Bye" session.alert.accept() diff --git a/tests/wpt/web-platform-tests/webdriver/tests/new_session/websocket_url.py b/tests/wpt/web-platform-tests/webdriver/tests/new_session/websocket_url.py new file mode 100644 index 00000000000..452decc90aa --- /dev/null +++ b/tests/wpt/web-platform-tests/webdriver/tests/new_session/websocket_url.py @@ -0,0 +1,7 @@ +from tests.support.asserts import assert_success + +def test_websocket_url(new_session, add_browser_capabilities): + response, _ = new_session({"capabilities": { + "alwaysMatch": add_browser_capabilities({"webSocketUrl": True})}}) + value = assert_success(response) + assert value["capabilities"]["webSocketUrl"].startswith("ws://") diff --git a/tests/wpt/web-platform-tests/webdriver/tests/support/fixtures.py b/tests/wpt/web-platform-tests/webdriver/tests/support/fixtures.py index 888c42b1f72..a1cd99a5540 100644 --- a/tests/wpt/web-platform-tests/webdriver/tests/support/fixtures.py +++ b/tests/wpt/web-platform-tests/webdriver/tests/support/fixtures.py @@ -2,6 +2,7 @@ import copy import json import os +import asyncio import pytest import webdriver @@ -24,6 +25,8 @@ def pytest_configure(config): # register the capabilities marker config.addinivalue_line("markers", "capabilities: mark test to use capabilities") + config.addinivalue_line("markers", + "bidi: mark test to use bidi") @pytest.fixture @@ -38,6 +41,20 @@ def pytest_generate_tests(metafunc): if marker: metafunc.parametrize("capabilities", marker.args, ids=None) + if "bidi" in metafunc.fixturenames: + marker = metafunc.definition.get_closest_marker(name="bidi") + if marker: + metafunc.parametrize("bidi", marker.args, ids=None) + +# Ensure that the event loop is restarted once per session rather than the default of once per test +# if we don't do this, tests will try to reuse a closed event loop and fail with an error that the "future +# belongs to a different loop" +@pytest.fixture(scope="session") +def event_loop(): + """Change event_loop fixture to session level.""" + loop = asyncio.get_event_loop_policy().new_event_loop() + yield loop + loop.close() @pytest.fixture def add_event_listeners(session): @@ -110,9 +127,12 @@ def configuration(): "capabilities": capabilities } +@pytest.fixture(scope="session") +def bidi(): + return False @pytest.fixture(scope="function") -def session(capabilities, configuration, request): +async def session(capabilities, configuration, request, bidi): """Create and start a session for a test that does not itself test session creation. By default the session will stay open after each test, but we always try to start a @@ -127,19 +147,35 @@ def session(capabilities, configuration, request): deep_update(caps, capabilities) caps = {"alwaysMatch": caps} - # If there is a session with different capabilities active, end it now + is_cur_bidi = isinstance(_current_session, webdriver.BidiSession) + # If there is a session with different capabilities active or the current session + # is of different type than the one we would like to create, end it now. if _current_session is not None and ( - caps != _current_session.requested_capabilities): - _current_session.end() + (not _current_session.match(caps)) or + (is_cur_bidi != bidi)): + if is_cur_bidi: + await _current_session.end() + else: + _current_session.end() _current_session = None if _current_session is None: - _current_session = webdriver.Session( - configuration["host"], - configuration["port"], - capabilities=caps) + if bidi: + _current_session = webdriver.BidiSession( + configuration["host"], + configuration["port"], + capabilities=caps) + else: + _current_session = webdriver.Session( + configuration["host"], + configuration["port"], + capabilities=caps) try: - _current_session.start() + if type(_current_session) == webdriver.BidiSession: + await _current_session.start() + else: + _current_session.start() + except webdriver.error.SessionNotCreatedException: if not _current_session.session_id: raise diff --git a/tests/wpt/web-platform-tests/webdriver/tests/switch_to_frame/switch.py b/tests/wpt/web-platform-tests/webdriver/tests/switch_to_frame/switch.py index df7e5b73af4..9ccab2c6c96 100644 --- a/tests/wpt/web-platform-tests/webdriver/tests/switch_to_frame/switch.py +++ b/tests/wpt/web-platform-tests/webdriver/tests/switch_to_frame/switch.py @@ -97,3 +97,20 @@ def test_frame_id_null(session, inline, iframe): frame = session.find.css("iframe", all=False) assert_same_element(session, frame, frame1) + + +def test_find_element_while_frame_is_still_loading(session, url): + session.timeouts.implicit = 5 + + frame_url = url("/webdriver/tests/support/html/subframe.html?pipe=trickle(d2)") + page_url = "<html><body><iframe src='{}'></iframe></body></html>".format(frame_url) + + session.execute_script( + "document.documentElement.innerHTML = arguments[0];", args=[page_url]) + + frame1 = session.find.css("iframe", all=False) + session.switch_frame(frame1) + + # Ensure that the is always a valid browsing context, and the element + # can be found eventually. + session.find.css("#delete", all=False) diff --git a/tests/wpt/web-platform-tests/webrtc/RTCDataChannel-iceRestart.html b/tests/wpt/web-platform-tests/webrtc/RTCDataChannel-iceRestart.html index 648c11b66a4..1aec50a5870 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCDataChannel-iceRestart.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCDataChannel-iceRestart.html @@ -14,6 +14,16 @@ async function checkCanPassData(channel1, channel2) { assert_equals(message, 'hello'); } +async function pingPongData(channel1, channel2, size=1) { + channel1.send('hello'); + const request = await awaitMessage(channel2); + assert_equals(request, 'hello'); + const response = 'x'.repeat(size); + channel2.send(response); + const responseReceived = await awaitMessage(channel1); + assert_equals(response, responseReceived); +} + promise_test(async t => { const pc1 = new RTCPeerConnection(); t.add_cleanup(() => pc1.close()); @@ -36,4 +46,31 @@ promise_test(async t => { channel2.close(); }, `Data channel remains usable after ICE restart`); +promise_test(async t => { + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc2.close()); + + const [channel1, channel2] = await createDataChannelPair(t, {}, pc1, pc2); + channel2.addEventListener('error', t.unreached_func()); + channel2.addEventListener('error', t.unreached_func()); + + await pingPongData(channel1, channel2); + pc1.restartIce(); + + await pc1.setLocalDescription(); + await pingPongData(channel1, channel2); + await pc2.setRemoteDescription(pc1.localDescription); + await pingPongData(channel1, channel2); + await pc2.setLocalDescription(await pc2.createAnswer()); + await pingPongData(channel1, channel2); + await pc1.setRemoteDescription(pc2.localDescription); + await pingPongData(channel1, channel2); + channel1.close(); + channel2.close(); +}, `Data channel remains usable at each step of an ICE restart`); + + + </script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-state.html b/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-state.html index d0230de0e64..33d49677c49 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-state.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCDtlsTransport-state.html @@ -86,4 +86,71 @@ promise_test(async t => { await resolveWhen(t, dtlsTransport2, 'closed'); }, 'close() causes the other end\'s DTLS transport to close'); +promise_test(async t => { + const config = {bundlePolicy: "max-bundle"}; + const pc1 = new RTCPeerConnection(config); + const pc2 = new RTCPeerConnection(config); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.onicecandidate = e => { + if (e.candidate) { + pc2.addIceCandidate({ + candidate: e.candidate.candidate, + sdpMid: e.candidate.sdpMid + }); + } + } + pc2.onicecandidate = e => { + if (e.candidate) { + pc1.addIceCandidate({ + candidate: e.candidate.candidate, + sdpMid: e.candidate.sdpMid + }); + } + } + + pc1.addTransceiver("video"); + pc1.addTransceiver("audio"); + await pc1.setLocalDescription(await pc1.createOffer()); + await pc2.setRemoteDescription(pc1.localDescription); + await pc2.setLocalDescription(await pc2.createAnswer()); + await pc1.setRemoteDescription(pc2.localDescription); + + const [videoTc, audioTc] = pc1.getTransceivers(); + const [videoTp, audioTp] = + pc1.getTransceivers().map(tc => tc.sender.transport); + + const [videoPc2Tp, audioPc2Tp] = + pc2.getTransceivers().map(tc => tc.sender.transport); + + assert_equals(pc1.getTransceivers().length, 2, 'pc1 transceiver count'); + assert_equals(pc2.getTransceivers().length, 2, 'pc2 transceiver count'); + assert_equals(videoTc.sender.transport, videoTc.receiver.transport); + assert_equals(videoTc.sender.transport, audioTc.sender.transport); + + await Promise.all([resolveWhen(t, videoTp, 'connected'), + resolveWhen(t, videoPc2Tp, 'connected')]); + + assert_equals(audioTc.sender, pc1.getSenders()[1]); + + let stoppedTransceiver = pc1.getTransceivers()[0]; + assert_equals(stoppedTransceiver, videoTc); // sanity + let onended = new Promise(resolve => { + stoppedTransceiver.receiver.track.onended = resolve; + }); + stoppedTransceiver.stop(); + await onended; + + assert_equals( + pc1.getReceivers().length, 1, + 'getReceivers does not expose a receiver of a stopped transceiver'); + assert_equals( + pc1.getSenders().length, 1, + 'getSenders does not expose a sender of a stopped transceiver'); + assert_equals(audioTc.sender, pc1.getSenders()[0]); // sanity + assert_equals(audioTc.sender.transport, audioTp); // sanity + assert_equals(audioTp.state, 'connected'); +}, 'stop bundled transceiver retains dtls transport state'); + </script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addIceCandidate.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addIceCandidate.html index 5fcb6e864a8..d8e24d608ba 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addIceCandidate.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-addIceCandidate.html @@ -158,18 +158,28 @@ a=rtcp-rsize null, // Members in the dictionary take their default values {} - ].forEach(init => promise_test(async t => { - const pc = new RTCPeerConnection(); + ].forEach(init => { + promise_test(async t => { + const pc = new RTCPeerConnection(); - t.add_cleanup(() => pc.close()); + t.add_cleanup(() => pc.close()); - await pc.setRemoteDescription(sessionDesc); - await pc.addIceCandidate(init); - assert_candidate_line_between(pc.remoteDescription.sdp, - mediaLine1, endOfCandidateLine, mediaLine2); - assert_candidate_line_after(pc.remoteDescription.sdp, - mediaLine2, endOfCandidateLine); - }, `addIceCandidate(${JSON.stringify(init)}) should work, and add a=end-of-candidates to both m-sections`)); + await pc.setRemoteDescription(sessionDesc); + await pc.addIceCandidate(init); + }, `addIceCandidate(${JSON.stringify(init)}) works`); + promise_test(async t => { + const pc = new RTCPeerConnection(); + + t.add_cleanup(() => pc.close()); + + await pc.setRemoteDescription(sessionDesc); + await pc.addIceCandidate(init); + assert_candidate_line_between(pc.remoteDescription.sdp, + mediaLine1, endOfCandidateLine, mediaLine2); + assert_candidate_line_after(pc.remoteDescription.sdp, + mediaLine2, endOfCandidateLine); + }, `addIceCandidate(${JSON.stringify(init)}) adds a=end-of-candidates to both m-sections`); + }); promise_test(async t => { const pc = new RTCPeerConnection(); @@ -418,7 +428,7 @@ a=rtcp-rsize assert_candidate_line_between(pc.remoteDescription.sdp, mediaLine1, endOfCandidateLine, mediaLine2); }); - }, 'Add with empty candidate string (end of candidate) should succeed'); + }, 'Add with empty candidate string (end of candidates) should succeed'); /* 4.4.2. addIceCandidate @@ -440,6 +450,15 @@ a=rtcp-rsize }))); }, 'Add candidate with both sdpMid and sdpMLineIndex manually set to null should reject with TypeError'); + promise_test(async t => { + const pc = new RTCPeerConnection(); + t.add_cleanup(() => pc.close()); + + await pc.setRemoteDescription(sessionDesc); + promise_rejects_js(t, TypeError, + pc.addIceCandidate({candidate: candidateStr1})); + }, 'addIceCandidate with a candidate and neither sdpMid nor sdpMLineIndex should reject with TypeError'); + promise_test(t => { const pc = new RTCPeerConnection(); @@ -609,5 +628,4 @@ a=rtcp-rsize usernameFragment: usernameFragment1 }))); }, 'Add candidate with sdpMid belonging to different usernameFragment should reject with OperationError'); - </script> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-capture-video.https.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-capture-video.https.html index 8d2579602a8..b6c0222dc21 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-capture-video.https.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-capture-video.https.html @@ -53,10 +53,10 @@ promise_test(async t => { // Wait until the video has non-zero resolution and some non-black pixels. await new Promise(p => { function checkColor() { - if (destVideo.width > 0 && getVideoSignal(destVideo).toFixed() > 0.0) + if (destVideo.videoWidth > 0 && getVideoSignal(destVideo) > 0.0) p(); else - t.step_timeout(checkColor, 0); + t.step_timeout(checkColor, 0); } checkColor(); }); @@ -64,9 +64,8 @@ promise_test(async t => { // Uses Helper.js GetVideoSignal to query |destVideo| pixel value at a certain position. const pixelValue = getVideoSignal(destVideo); - // Expected value computed based on GetVideoSignal code, which takes green pixel data - // with coefficient 0.72. - assert_approx_equals(pixelValue, 0.72*255, 2); + // Anything non-black means that capture works. + assert_not_equals(pixelValue, 0); }, "Capturing a video element and sending it via PeerConnection"); </script> </body> diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-helper.js b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-helper.js index 5aef0a8e21e..cdfe63e792c 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-helper.js +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-helper.js @@ -242,8 +242,7 @@ async function waitForIceGatheringState(pc, wantedStates) { async function listenForSSRCs(t, receiver) { while (true) { const ssrcs = receiver.getSynchronizationSources(); - assert_true(Array.isArray(ssrcs)); - if (ssrcs.length > 0) { + if (Array.isArray(ssrcs) && ssrcs.length > 0) { return ssrcs; } await new Promise(r => t.step_timeout(r, 0)); diff --git a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-restartIce.https.html b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-restartIce.https.html index 8f8c661a41d..dc5e1674576 100644 --- a/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-restartIce.https.html +++ b/tests/wpt/web-platform-tests/webrtc/RTCPeerConnection-restartIce.https.html @@ -16,15 +16,36 @@ function getLines(sdp, startsWith) { const getUfrags = ({sdp}) => getLines(sdp, "a=ice-ufrag:"); const getPwds = ({sdp}) => getLines(sdp, "a=ice-pwd:"); -async function exchangeOfferAnswerEndOnFirst(pc1, pc2) { - await pc1.setLocalDescription(await pc1.createOffer()); +const negotiators = [ + { + tag: "", + async setOffer(pc) { + await pc.setLocalDescription(await pc.createOffer()); + }, + async setAnswer(pc) { + await pc.setLocalDescription(await pc.createAnswer()); + }, + }, + { + tag: " (perfect negotiation)", + async setOffer(pc) { + await pc.setLocalDescription(); + }, + async setAnswer(pc) { + await pc.setLocalDescription(); + }, + }, +]; + +async function exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator) { + await negotiator.setOffer(pc1); await pc2.setRemoteDescription(pc1.localDescription); - await pc2.setLocalDescription(await pc2.createAnswer()); + await negotiator.setAnswer(pc2); await pc1.setRemoteDescription(pc2.localDescription); // End on pc1. No race } -async function exchangeOfferAnswerEndOnSecond(pc1, pc2) { - await pc1.setLocalDescription(await pc1.createOffer()); +async function exchangeOfferAnswerEndOnSecond(pc1, pc2, negotiator) { + await negotiator.setOffer(pc1); await pc2.setRemoteDescription(pc1.localDescription); await pc1.setRemoteDescription(await pc2.createAnswer()); await pc2.setLocalDescription(pc1.remoteDescription); // End on pc2. No race @@ -65,330 +86,404 @@ promise_test(async t => { await assertNoNegotiationNeeded(t, pc1); }, "restartIce() does not trigger negotiation ahead of initial negotiation"); -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - pc1.restartIce(); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() has no effect on initial negotiation"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - pc1.restartIce(); - await new Promise(r => pc1.onnegotiationneeded = r); -}, "restartIce() fires negotiationneeded after initial negotiation"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "control 1"); - assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "control 2"); - - pc1.restartIce(); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); - - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - assert_ufrags_equals(getUfrags(pc1.localDescription)[0], newUfrag1, "Unchanged 1"); - assert_ufrags_equals(getUfrags(pc2.localDescription)[0], newUfrag2, "Unchanged 2"); -}, "restartIce() causes fresh ufrags"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); - - await pc1.setLocalDescription(await pc1.createOffer()); - pc1.restartIce(); - await pc2.setRemoteDescription(pc1.localDescription); - await pc2.setLocalDescription(await pc2.createAnswer()); - // Several tests in this file initializes the onnegotiationneeded listener - // before the setLocalDescription() or setRemoteDescription() that we expect - // to trigger negotiation needed. This allows Chrome to exercise these tests - // without timing out due to a bug that causes onnegotiationneeded to fire too - // early. - // TODO(https://crbug.com/985797): Once Chrome does not fire ONN too early, - // simply do "await new Promise(...)" instead of - // "await negotiationNeededPromise" here and in other tests in this file. - const negotiationNeededPromise = - new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setRemoteDescription(pc2.localDescription); - assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "Unchanged 1"); - assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "Unchanged 2"); - await negotiationNeededPromise; - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() works in have-local-offer"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setLocalDescription(await pc1.createOffer()); - pc1.restartIce(); - await pc2.setRemoteDescription(pc1.localDescription); - await pc2.setLocalDescription(await pc2.createAnswer()); - const negotiationNeededPromise = - new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setRemoteDescription(pc2.localDescription); - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); - await negotiationNeededPromise; - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() works in initial have-local-offer"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); - - await pc2.setLocalDescription(await pc2.createOffer()); - await pc1.setRemoteDescription(pc2.localDescription); - pc1.restartIce(); - await pc2.setRemoteDescription(await pc1.createAnswer()); - const negotiationNeededPromise = - new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setLocalDescription(pc2.remoteDescription); // End on pc1. No race - assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "Unchanged 1"); - assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "Unchanged 2"); - await negotiationNeededPromise; - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() works in have-remote-offer"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc2.addTransceiver("audio"); - await pc2.setLocalDescription(await pc2.createOffer()); - await pc1.setRemoteDescription(pc2.localDescription); - pc1.restartIce(); - await pc2.setRemoteDescription(await pc1.createAnswer()); - await pc1.setLocalDescription(pc2.remoteDescription); // End on pc1. No race - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() does nothing in initial have-remote-offer"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); - - pc1.restartIce(); - await new Promise(r => pc1.onnegotiationneeded = r); - const negotiationNeededPromise = - new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnSecond(pc2, pc1); - assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "nothing yet 1"); - assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "nothing yet 2"); - await negotiationNeededPromise; - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag2, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() survives remote offer"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); +// Run remaining tests twice: once for each negotiator + +for (const negotiator of negotiators) { + const {tag} = negotiator; + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + pc1.restartIce(); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() has no effect on initial negotiation${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + pc1.restartIce(); + await new Promise(r => pc1.onnegotiationneeded = r); + }, `restartIce() fires negotiationneeded after initial negotiation${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "control 1"); + assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "control 2"); + + pc1.restartIce(); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + assert_ufrags_equals(getUfrags(pc1.localDescription)[0], newUfrag1, "Unchanged 1"); + assert_ufrags_equals(getUfrags(pc2.localDescription)[0], newUfrag2, "Unchanged 2"); + }, `restartIce() causes fresh ufrags${tag}`); + + promise_test(async t => { + const config = {bundlePolicy: "max-bundle"}; + const pc1 = new RTCPeerConnection(config); + const pc2 = new RTCPeerConnection(config); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.onicecandidate = e => { + if (e.candidate) { + pc2.addIceCandidate({ + candidate: e.candidate.candidate, + sdpMid: e.candidate.sdpMid + }); + } + } + pc2.onicecandidate = e => { + if (e.candidate) { + pc1.addIceCandidate({ + candidate: e.candidate.candidate, + sdpMid: e.candidate.sdpMid + }); + } + } + + // See the explanation below about Chrome's onnegotiationneeded firing + // too early. + const negotiationNeededPromise1 = + new Promise(r => pc1.onnegotiationneeded = r); + pc1.addTransceiver("video"); + pc1.addTransceiver("audio"); + await negotiationNeededPromise1; + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [videoTc, audioTc] = pc1.getTransceivers(); + const [videoTp, audioTp] = + pc1.getTransceivers().map(tc => tc.sender.transport); + assert_equals(pc1.getTransceivers().length, 2, 'transceiver count'); + + // On Chrome, it is possible (likely, even) that videoTc.sender.transport.state + // will be 'connected' by the time we get here. We'll race 2 promises here: + // 1. Resolve after onstatechange is called with connected state. + // 2. If already connected, resolve immediately. + await Promise.race([ + new Promise(r => videoTc.sender.transport.onstatechange = + () => videoTc.sender.transport.state == "connected" && r()), + new Promise(r => videoTc.sender.transport.state == "connected" && r()) + ]); + assert_equals(videoTc.sender.transport.state, "connected"); + + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + assert_equals(videoTp, pc1.getTransceivers()[0].sender.transport, + 'offer/answer retains dtls transport'); + assert_equals(audioTp, pc1.getTransceivers()[1].sender.transport, + 'offer/answer retains dtls transport'); + + const negotiationNeededPromise2 = + new Promise(r => pc1.onnegotiationneeded = r); + pc1.restartIce(); + await negotiationNeededPromise2; + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [newVideoTp, newAudioTp] = + pc1.getTransceivers().map(tc => tc.sender.transport); + assert_equals(videoTp, newVideoTp, 'ice restart retains dtls transport'); + assert_equals(audioTp, newAudioTp, 'ice restart retains dtls transport'); + }, `restartIce() retains dtls transports${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + + await negotiator.setOffer(pc1); + pc1.restartIce(); + await pc2.setRemoteDescription(pc1.localDescription); + await negotiator.setAnswer(pc2); + // Several tests in this file initializes the onnegotiationneeded listener + // before the setLocalDescription() or setRemoteDescription() that we expect + // to trigger negotiation needed. This allows Chrome to exercise these tests + // without timing out due to a bug that causes onnegotiationneeded to fire too + // early. + // TODO(https://crbug.com/985797): Once Chrome does not fire ONN too early, + // simply do "await new Promise(...)" instead of + // "await negotiationNeededPromise" here and in other tests in this file. + const negotiationNeededPromise = + new Promise(r => pc1.onnegotiationneeded = r); + await pc1.setRemoteDescription(pc2.localDescription); + assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "Unchanged 1"); + assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "Unchanged 2"); + await negotiationNeededPromise; + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() works in have-local-offer${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await negotiator.setOffer(pc1); + pc1.restartIce(); + await pc2.setRemoteDescription(pc1.localDescription); + await negotiator.setAnswer(pc2); + const negotiationNeededPromise = + new Promise(r => pc1.onnegotiationneeded = r); + await pc1.setRemoteDescription(pc2.localDescription); + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + await negotiationNeededPromise; + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() works in initial have-local-offer${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + + await negotiator.setOffer(pc2); + await pc1.setRemoteDescription(pc2.localDescription); + pc1.restartIce(); + await pc2.setRemoteDescription(await pc1.createAnswer()); + const negotiationNeededPromise = + new Promise(r => pc1.onnegotiationneeded = r); + await pc1.setLocalDescription(pc2.remoteDescription); // End on pc1. No race + assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "Unchanged 1"); + assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "Unchanged 2"); + await negotiationNeededPromise; + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() works in have-remote-offer${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc2.addTransceiver("audio"); + await negotiator.setOffer(pc2); + await pc1.setRemoteDescription(pc2.localDescription); + pc1.restartIce(); + await pc2.setRemoteDescription(await pc1.createAnswer()); + await pc1.setLocalDescription(pc2.remoteDescription); // End on pc1. No race + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() does nothing in initial have-remote-offer${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + + pc1.restartIce(); + await new Promise(r => pc1.onnegotiationneeded = r); + const negotiationNeededPromise = + new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnSecond(pc2, pc1, negotiator); + assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "nothing yet 1"); + assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "nothing yet 2"); + await negotiationNeededPromise; + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag2, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() survives remote offer${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + + pc1.restartIce(); + pc2.restartIce(); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnSecond(pc2, pc1, negotiator); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + assert_ufrags_equals(getUfrags(pc1.localDescription)[0], newUfrag1, "Unchanged 1"); + assert_ufrags_equals(getUfrags(pc2.localDescription)[0], newUfrag2, "Unchanged 2"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() is satisfied by remote ICE restart${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + + pc1.restartIce(); + await new Promise(r => pc1.onnegotiationneeded = r); + await pc1.setLocalDescription(await pc1.createOffer({iceRestart: false})); + await pc2.setRemoteDescription(pc1.localDescription); + await negotiator.setAnswer(pc2); + await pc1.setRemoteDescription(pc2.localDescription); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() trumps {iceRestart: false}${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection(); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const [oldUfrag1] = getUfrags(pc1.localDescription); + const [oldUfrag2] = getUfrags(pc2.localDescription); + + pc1.restartIce(); + await new Promise(r => pc1.onnegotiationneeded = r); + await negotiator.setOffer(pc1); + const negotiationNeededPromise = + new Promise(r => pc1.onnegotiationneeded = r); + await pc1.setLocalDescription({type: "rollback"}); + await negotiationNeededPromise; + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + const [newUfrag1] = getUfrags(pc1.localDescription); + const [newUfrag2] = getUfrags(pc2.localDescription); + assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); + assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() survives rollback${tag}`); + + promise_test(async t => { + const pc1 = new RTCPeerConnection({bundlePolicy: "max-compat"}); + const pc2 = new RTCPeerConnection({bundlePolicy: "max-compat"}); + t.add_cleanup(() => pc1.close()); + t.add_cleanup(() => pc2.close()); + + pc1.addTransceiver("audio"); + pc1.addTransceiver("video"); + await new Promise(r => pc1.onnegotiationneeded = r); + await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator); + + const oldUfrags1 = getUfrags(pc1.localDescription); + const oldUfrags2 = getUfrags(pc2.localDescription); + const oldPwds2 = getPwds(pc2.localDescription); + + pc1.restartIce(); + await new Promise(r => pc1.onnegotiationneeded = r); + + // Engineer a partial ICE restart from pc2 + pc2.restartIce(); + await negotiator.setOffer(pc2); + { + let {type, sdp} = pc2.localDescription; + // Restore both old ice-ufrag and old ice-pwd to trigger a partial restart + sdp = sdp.replace(getUfrags({sdp})[0], oldUfrags2[0]); + sdp = sdp.replace(getPwds({sdp})[0], oldPwds2[0]); + const newUfrags2 = getUfrags({sdp}); + const newPwds2 = getPwds({sdp}); + assert_ufrags_equals(newUfrags2[0], oldUfrags2[0], "control ufrag match"); + assert_ufrags_equals(newPwds2[0], oldPwds2[0], "control pwd match"); + assert_ufrags_not_equals(newUfrags2[1], oldUfrags2[1], "control ufrag non-match"); + assert_ufrags_not_equals(newPwds2[1], oldPwds2[1], "control pwd non-match"); + await pc1.setRemoteDescription({type, sdp}); + } + const negotiationNeededPromise = + new Promise(r => pc1.onnegotiationneeded = r); + await negotiator.setAnswer(pc1); + const newUfrags1 = getUfrags(pc1.localDescription); + assert_ufrags_equals(newUfrags1[0], oldUfrags1[0], "Unchanged 1"); + assert_ufrags_not_equals(newUfrags1[1], oldUfrags1[1], "Restarted 2"); + await negotiationNeededPromise; + await negotiator.setOffer(pc1); + const newestUfrags1 = getUfrags(pc1.localDescription); + assert_ufrags_not_equals(newestUfrags1[0], oldUfrags1[0], "Restarted 1"); + assert_ufrags_not_equals(newestUfrags1[1], oldUfrags1[1], "Restarted 2"); + await assertNoNegotiationNeeded(t, pc1); + }, `restartIce() survives remote offer containing partial restart${tag}`); - pc1.restartIce(); - pc2.restartIce(); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnSecond(pc2, pc1); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); - - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - assert_ufrags_equals(getUfrags(pc1.localDescription)[0], newUfrag1, "Unchanged 1"); - assert_ufrags_equals(getUfrags(pc2.localDescription)[0], newUfrag2, "Unchanged 2"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() is satisfied by remote ICE restart"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); - - pc1.restartIce(); - await new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setLocalDescription(await pc1.createOffer({iceRestart: false})); - await pc2.setRemoteDescription(pc1.localDescription); - await pc2.setLocalDescription(await pc2.createAnswer()); - await pc1.setRemoteDescription(pc2.localDescription); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() trumps {iceRestart: false}"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection(); - const pc2 = new RTCPeerConnection(); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const [oldUfrag1] = getUfrags(pc1.localDescription); - const [oldUfrag2] = getUfrags(pc2.localDescription); - - pc1.restartIce(); - await new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setLocalDescription(await pc1.createOffer()); - const negotiationNeededPromise = - new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setLocalDescription({type: "rollback"}); - await negotiationNeededPromise; - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - const [newUfrag1] = getUfrags(pc1.localDescription); - const [newUfrag2] = getUfrags(pc2.localDescription); - assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed"); - assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() survives rollback"); - -promise_test(async t => { - const pc1 = new RTCPeerConnection({bundlePolicy: "max-compat"}); - const pc2 = new RTCPeerConnection({bundlePolicy: "max-compat"}); - t.add_cleanup(() => pc1.close()); - t.add_cleanup(() => pc2.close()); - - pc1.addTransceiver("audio"); - pc1.addTransceiver("video"); - await new Promise(r => pc1.onnegotiationneeded = r); - await exchangeOfferAnswerEndOnFirst(pc1, pc2); - - const oldUfrags1 = getUfrags(pc1.localDescription); - const oldUfrags2 = getUfrags(pc2.localDescription); - const oldPwds2 = getPwds(pc2.localDescription); - - pc1.restartIce(); - await new Promise(r => pc1.onnegotiationneeded = r); - - // Engineer a partial ICE restart from pc2 - pc2.restartIce(); - await pc2.setLocalDescription(await pc2.createOffer()); - { - let {type, sdp} = pc2.localDescription; - // Restore both old ice-ufrag and old ice-pwd to trigger a partial restart - sdp = sdp.replace(getUfrags({sdp})[0], oldUfrags2[0]); - sdp = sdp.replace(getPwds({sdp})[0], oldPwds2[0]); - const newUfrags2 = getUfrags({sdp}); - const newPwds2 = getPwds({sdp}); - assert_ufrags_equals(newUfrags2[0], oldUfrags2[0], "control ufrag match"); - assert_ufrags_equals(newPwds2[0], oldPwds2[0], "control pwd match"); - assert_ufrags_not_equals(newUfrags2[1], oldUfrags2[1], "control ufrag non-match"); - assert_ufrags_not_equals(newPwds2[1], oldPwds2[1], "control pwd non-match"); - await pc1.setRemoteDescription({type, sdp}); - } - const negotiationNeededPromise = - new Promise(r => pc1.onnegotiationneeded = r); - await pc1.setLocalDescription(await pc1.createAnswer()); - const newUfrags1 = getUfrags(pc1.localDescription); - assert_ufrags_equals(newUfrags1[0], oldUfrags1[0], "Unchanged 1"); - assert_ufrags_not_equals(newUfrags1[1], oldUfrags1[1], "Restarted 2"); - await negotiationNeededPromise; - await pc1.setLocalDescription(await pc1.createOffer()); - const newestUfrags1 = getUfrags(pc1.localDescription); - assert_ufrags_not_equals(newestUfrags1[0], oldUfrags1[0], "Restarted 1"); - assert_ufrags_not_equals(newestUfrags1[1], oldUfrags1[1], "Restarted 2"); - await assertNoNegotiationNeeded(t, pc1); -}, "restartIce() survives remote offer containing partial restart"); +} </script> diff --git a/tests/wpt/web-platform-tests/webrtc/protocol/handover-datachannel.html b/tests/wpt/web-platform-tests/webrtc/protocol/handover-datachannel.html new file mode 100644 index 00000000000..8f224f822a7 --- /dev/null +++ b/tests/wpt/web-platform-tests/webrtc/protocol/handover-datachannel.html @@ -0,0 +1,61 @@ +<!doctype html> +<meta charset=utf-8> +<title>RTCPeerConnection Handovers</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../RTCPeerConnection-helper.js"></script> +<script> +'use strict'; + +promise_test(async t => { + const offerPc = new RTCPeerConnection(); + const answerPcFirst = new RTCPeerConnection(); + const answerPcSecond = new RTCPeerConnection(); + t.add_cleanup(() => { + offerPc.close(); + answerPcFirst.close(); + answerPcSecond.close(); + }); + const offerDatachannel1 = offerPc.createDataChannel('initial'); + exchangeIceCandidates(offerPc, answerPcFirst); + + // Negotiate connection with PC 1 + const offer1 = await offerPc.createOffer(); + await offerPc.setLocalDescription(offer1); + await answerPcFirst.setRemoteDescription(offer1); + const answer1 = await answerPcFirst.createAnswer(); + await offerPc.setRemoteDescription(answer1); + await answerPcFirst.setLocalDescription(answer1); + const datachannelAtAnswerPcFirst = await new Promise( + r => answerPcFirst.ondatachannel = ({channel}) => r(channel)); + const iceTransport = offerPc.sctp.transport; + // Check that messages get through. + datachannelAtAnswerPcFirst.send('hello'); + const message1 = await awaitMessage(offerDatachannel1); + assert_equals(message1, 'hello'); + + // Renegotiate with PC 2 + // Note - ICE candidates will also be sent to answerPc1, but that shouldn't matter. + exchangeIceCandidates(offerPc, answerPcSecond); + const offer2 = await offerPc.createOffer(); + await offerPc.setLocalDescription(offer2); + await answerPcSecond.setRemoteDescription(offer2); + const answer2 = await answerPcSecond.createAnswer(); + await offerPc.setRemoteDescription(answer2); + await answerPcSecond.setLocalDescription(answer2); + + // Kill the first PC. This should not affect anything, but leaving it may cause untoward events. + answerPcFirst.close(); + + const answerDataChannel2 = answerPcSecond.createDataChannel('second back'); + + const datachannelAtOfferPcSecond = await new Promise(r => offerPc.ondatachannel = ({channel}) => r(channel)); + + await new Promise(r => datachannelAtOfferPcSecond.onopen = r); + + datachannelAtOfferPcSecond.send('hello again'); + const message2 = await awaitMessage(answerDataChannel2); + assert_equals(message2, 'hello again'); +}, 'Handover with datachannel reinitiated from new callee completes'); + +</script> diff --git a/tests/wpt/web-platform-tests/webrtc/protocol/handover.html b/tests/wpt/web-platform-tests/webrtc/protocol/handover.html index 378d65ffc32..748cbeff8d7 100644 --- a/tests/wpt/web-platform-tests/webrtc/protocol/handover.html +++ b/tests/wpt/web-platform-tests/webrtc/protocol/handover.html @@ -69,6 +69,4 @@ promise_test(async t => { await offerPc.setLocalDescription(answer2); }, 'Negotiation of handover initiated at callee works'); - - </script> diff --git a/tests/wpt/web-platform-tests/webtransport/quic/handlers/README.md b/tests/wpt/web-platform-tests/webtransport/quic/handlers/README.md index 22073e783a2..0584d8f4bfd 100644 --- a/tests/wpt/web-platform-tests/webtransport/quic/handlers/README.md +++ b/tests/wpt/web-platform-tests/webtransport/quic/handlers/README.md @@ -1,2 +1,2 @@ This directory contains custom handlers for testing QuicTransport. Please see -https://github.com/web-platform-tests/wpt/tools/quic.
\ No newline at end of file +https://github.com/web-platform-tests/wpt/tree/master/tools/quic. diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_dataUnavailable.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_dataUnavailable.https.html new file mode 100644 index 00000000000..e120f0b7dd1 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_dataUnavailable.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../dataUnavailableTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, + depthSensingData: DEPTH_SENSING_DATA, +}; + +xr_session_promise_test("Ensures depth data is not available when cleared in the controller, `cpu-optimized`", + dataUnavailableTestFunctionGenerator(/*isCpuOptimized=*/true), + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_CPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_inactiveFrame.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_inactiveFrame.https.html new file mode 100644 index 00000000000..92c20cecbf6 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_inactiveFrame.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../inactiveFrameTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, +}; + +xr_session_promise_test("Ensures getDepthInformation() throws when not run in an active frame, `cpu-optimized`", + inactiveFrameTestFunctionGenerator(/*isCpuOptimized=*/true), + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_CPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_incorrectUsage.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_incorrectUsage.https.html new file mode 100644 index 00000000000..44868d8cb0e --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_incorrectUsage.https.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../incorrectUsageTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, +}; + +const incorrectUsagetestFunctionTryGetWebGLOnCpu = function (session, controller, t, sessionObjects) { + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + let done = false; + + const glBinding = new XRWebGLBinding(session, sessionObjects.gl); + + session.requestAnimationFrame((time, frame) => { + const pose = frame.getViewerPose(viewerSpace); + for(const view of pose.views) { + t.step(() => { + assert_throws_dom("InvalidStateError", () => glBinding.getDepthInformation(view), + "XRWebGLBinding.getDepthInformation() should throw when depth sensing is in `cpu-optimized` usage mode"); + }); + } + + done = true; + }); + + return t.step_wait(() => done); + }); +}; + +xr_session_promise_test("Ensures XRWebGLDepthInformation is not obtainable in `cpu-optimized` usage mode", + incorrectUsagetestFunctionTryGetWebGLOnCpu, + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_CPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_luminance_alpha_dataValid.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_luminance_alpha_dataValid.https.html new file mode 100644 index 00000000000..3bda9e21d7c --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_luminance_alpha_dataValid.https.html @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_math_utils.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, + depthSensingData: DEPTH_SENSING_DATA, +}; + +const assert_depth_valid_at = function(depthInformation, row, column, deltaRow, deltaColumn) { + // column and row correspond to the depth buffer coordinates, + // *not* to normalized view coordinates the getDepthInMeters() expects. + + const expectedValue = getExpectedValueAt(column, row); + + // 1. Normalize: + let x = (column + deltaColumn) / depthInformation.width; + let y = (row + deltaRow) / depthInformation.height; + + // 2. Apply the transform that changes the origin and axes: + x = 1.0 - x; + y = 1.0 - y; + + const depthValue = depthInformation.getDepthInMeters(x, y); + assert_approx_equals(depthValue, expectedValue, FLOAT_EPSILON, + "Depth value at (" + column + "," + row + "), deltas=(" + deltaColumn + ", " + deltaRow + "), " + + "coordinates (" + x + "," + y + ") must match!"); +} + +const assert_depth_valid = function(depthInformation) { + for(let row = 0; row < depthInformation.height; row++) { + for(let column = 0; column < depthInformation.width; column++) { + // middle of the pixel: + assert_depth_valid_at(depthInformation, row, column, 0.5, 0.5); + + // corners of the pixel: + assert_depth_valid_at(depthInformation, row, column, FLOAT_EPSILON, FLOAT_EPSILON); + assert_depth_valid_at(depthInformation, row, column, FLOAT_EPSILON, 1 - FLOAT_EPSILON); + assert_depth_valid_at(depthInformation, row, column, 1 - FLOAT_EPSILON, FLOAT_EPSILON); + assert_depth_valid_at(depthInformation, row, column, 1 - FLOAT_EPSILON, 1 - FLOAT_EPSILON); + } + } + + // Verify out-of-bounds accesses throw: + assert_throws_js(RangeError, + () => depthInformation.getDepthInMeters(-FLOAT_EPSILON, 0.0), + "getDepthInMeters() should throw when run with invalid indices - negative x"); + assert_throws_js(RangeError, + () => depthInformation.getDepthInMeters(0.0, -FLOAT_EPSILON), + "getDepthInMeters() should throw when run with invalid indices - negative y"); + assert_throws_js(RangeError, + () => depthInformation.getDepthInMeters(1+FLOAT_EPSILON, 0.0), + "getDepthInMeters() should throw when run with invalid indices - too big x"); + assert_throws_js(RangeError, + () => depthInformation.getDepthInMeters(0.0, 1+FLOAT_EPSILON), + "getDepthInMeters() should throw when run with invalid indices - too big y"); +}; + +const testCpuOptimizedLuminanceAlpha = function(session, fakeDeviceController, t) { + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + let done = false; + + const rafCallback = function(time, frame) { + const pose = frame.getViewerPose(viewerSpace); + if(pose) { + for(const view of pose.views) { + const depthInformation = frame.getDepthInformation(view); + + t.step(() => { + assert_not_equals(depthInformation, null, "XRCPUDepthInformation must not be null!"); + assert_approx_equals(depthInformation.width, DEPTH_SENSING_DATA.width, FLOAT_EPSILON); + assert_approx_equals(depthInformation.height, DEPTH_SENSING_DATA.height, FLOAT_EPSILON); + assert_approx_equals(depthInformation.rawValueToMeters, DEPTH_SENSING_DATA.rawValueToMeters, FLOAT_EPSILON); + assert_transform_approx_equals(depthInformation.normDepthBufferFromNormView, DEPTH_SENSING_DATA.normDepthBufferFromNormView); + assert_depth_valid(depthInformation); + }); + } + } + + done = true; + }; + + session.requestAnimationFrame(rafCallback); + + return t.step_wait(() => done); + }); +}; + +xr_session_promise_test("Ensures depth data is returned and values match expectation, cpu-optimized, luminance-alpha.", + testCpuOptimizedLuminanceAlpha, + fakeDeviceInitParams, + 'immersive-ar', { + 'requiredFeatures': ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_CPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_staleView.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_staleView.https.html new file mode 100644 index 00000000000..6a411ace451 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/cpu/depth_sensing_cpu_staleView.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../staleViewsTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, +}; + +xr_session_promise_test("Ensures getDepthInformation() throws when run with stale XRView, `cpu-optimized`", + staleViewsTestFunctionGenerator(/*isCpuOptimized=*/true), + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_CPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/dataUnavailableTests.js b/tests/wpt/web-platform-tests/webxr/depth-sensing/dataUnavailableTests.js new file mode 100644 index 00000000000..7460af71326 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/dataUnavailableTests.js @@ -0,0 +1,58 @@ +'use strict'; + +const TestStates = Object.freeze({ + "ShouldSucceedScheduleRAF": 1, + "ShouldFailScheduleRAF": 2, + "ShouldSucceedTestDone": 3, +}); + +const dataUnavailableTestFunctionGenerator = function(isCpuOptimized) { + return (session, controller, t, sessionObjects) => { + let state = TestStates.ShouldSucceedScheduleRAF; + + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + let done = false; + + const glBinding = new XRWebGLBinding(session, sessionObjects.gl); + + const rafCb = function(time, frame) { + const pose = frame.getViewerPose(viewerSpace); + for(const view of pose.views) { + const depthInformation = isCpuOptimized ? frame.getDepthInformation(view) + : glBinding.getDepthInformation(view); + + if (state == TestStates.ShouldSucceedScheduleRAF + || state == TestStates.ShouldSucceedTestDone) { + t.step(() => { + assert_not_equals(depthInformation, null); + }); + } else { + t.step(() => { + assert_equals(depthInformation, null); + }); + } + } + + switch(state) { + case TestStates.ShouldSucceedScheduleRAF: + controller.clearDepthSensingData(); + state = TestStates.ShouldFailScheduleRAF; + session.requestAnimationFrame(rafCb); + break; + case TestStates.ShouldFailScheduleRAF: + controller.setDepthSensingData(DEPTH_SENSING_DATA); + state = TestStates.ShouldSucceedTestDone; + session.requestAnimationFrame(rafCb); + break; + case TestStates.ShouldSucceedTestDone: + done = true; + break; + } + }; + + session.requestAnimationFrame(rafCb); + + return t.step_wait(() => done); + }); + }; +};
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/depth_sensing_notEnabled.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/depth_sensing_notEnabled.https.html new file mode 100644 index 00000000000..23bae35493a --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/depth_sensing_notEnabled.https.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="../resources/webxr_util.js"></script> +<script src="../resources/webxr_test_constants.js"></script> + +<script> + +const testFunctionCpu = function (session, controller, t) { + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + let done = false; + + session.requestAnimationFrame((time, frame) => { + const pose = frame.getViewerPose(viewerSpace); + for(const view of pose.views) { + assert_throws_dom("NotSupportedError", () => frame.getDepthInformation(view), + "getDepthInformation() should throw when depth sensing is disabled"); + } + + done = true; + }); + + return t.step_wait(() => done); + }); +}; + +const testFunctionGpu = function (session, controller, t, sessionObjects) { + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + let done = false; + + const glBinding = new XRWebGLBinding(session, sessionObjects.gl); + + session.requestAnimationFrame((time, frame) => { + const pose = frame.getViewerPose(viewerSpace); + for(const view of pose.views) { + t.step(() => { + assert_throws_dom("NotSupportedError", () => glBinding.getDepthInformation(view), + "getDepthInformation() should throw when depth sensing is disabled"); + }); + } + + done = true; + }); + + return t.step_wait(() => done); + }); +}; + +xr_session_promise_test( + "XRFrame.getDepthInformation() rejects if depth sensing is not enabled on a session", + testFunctionCpu, + IMMERSIVE_AR_DEVICE, + 'immersive-ar'); + +xr_session_promise_test( + "XRWebGLBinding.getDepthInformation() rejects if depth sensing is not enabled on a session", + testFunctionGpu, + IMMERSIVE_AR_DEVICE, + 'immersive-ar'); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_dataUnavailable.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_dataUnavailable.https.html new file mode 100644 index 00000000000..018edf76934 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_dataUnavailable.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../dataUnavailableTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, + depthSensingData: DEPTH_SENSING_DATA, +}; + +xr_session_promise_test("Ensures depth data is not available when cleared in the controller, `gpu-optimized`", + dataUnavailableTestFunctionGenerator(/*isCpuOptimized=*/false), + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_GPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_inactiveFrame.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_inactiveFrame.https.html new file mode 100644 index 00000000000..6116f7a0414 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_inactiveFrame.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../inactiveFrameTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, +}; + +xr_session_promise_test("Ensures getDepthInformation() throws when not run in an active frame, `gpu-optimized`", + testFunctionGenerator(/*isCpuOptimized=*/false), + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_GPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_incorrectUsage.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_incorrectUsage.https.html new file mode 100644 index 00000000000..9fc2e6a5f83 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_incorrectUsage.https.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../incorrectUsageTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, +}; + +const incorrectUsageTestFunctionTryGetCpuOnGpu = function (session, controller, t, sessionObjects) { + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + let done = false; + + session.requestAnimationFrame((time, frame) => { + const pose = frame.getViewerPose(viewerSpace); + for(const view of pose.views) { + t.step(() => { + assert_throws_dom("InvalidStateError", () => frame.getDepthInformation(view), + "XRFrame.getDepthInformation() should throw when depth sensing is in `gpu-optimized` usage mode"); + }); + } + + done = true; + }); + + return t.step_wait(() => done); + }); +}; + +xr_session_promise_test("Ensures XRCPUDepthInformation is not obtainable in `gpu-optimized` usage mode", + incorrectUsageTestFunctionTryGetCpuOnGpu, + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_GPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_staleView.https.html b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_staleView.https.html new file mode 100644 index 00000000000..ecd0d479f7a --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/gpu/depth_sensing_gpu_staleView.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../resources/webxr_util.js"></script> +<script src="../../resources/webxr_test_asserts.js"></script> +<script src="../../resources/webxr_test_constants.js"></script> +<script src="../../resources/webxr_test_constants_fake_depth.js"></script> +<script src="../staleViewsTests.js"></script> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + supportedFeatures: ALL_FEATURES, +}; + +xr_session_promise_test("Ensures getDepthInformation() throws when not run with stale XRView, `gpu-optimized`", + staleViewsTestFunctionGenerator(/*isCpuOptimized=*/false), + fakeDeviceInitParams, + 'immersive-ar', { + requiredFeatures: ['depth-sensing'], + depthSensing: VALID_DEPTH_CONFIG_GPU_USAGE, + }); + +</script> diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/inactiveFrameTests.js b/tests/wpt/web-platform-tests/webxr/depth-sensing/inactiveFrameTests.js new file mode 100644 index 00000000000..b310f01ef87 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/inactiveFrameTests.js @@ -0,0 +1,36 @@ +'use strict'; + +const inactiveFrameTestFunctionGenerator = function(isCpuOptimized) { + return (session, controller, t, sessionObjects) => { + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + let callbacksKickedOff = false; + let callbackCounter = 0; + + const glBinding = new XRWebGLBinding(session, sessionObjects.gl); + + const rafCb = function(time, frame) { + const pose = frame.getViewerPose(viewerSpace); + for(const view of pose.views) { + const callback = () => { + t.step(() => { + assert_throws_dom("InvalidStateError", + () => isCpuOptimized ? frame.getDepthInformation(view) + : glBinding.getDepthInformation(view), + "getDepthInformation() should throw when ran outside RAF"); + }); + callbackCounter--; + } + + t.step_timeout(callback, 10); + callbackCounter++; + } + + callbacksKickedOff = true; + }; + + session.requestAnimationFrame(rafCb); + + return t.step_wait(() => callbacksKickedOff && (callbackCounter == 0)); + }); + }; +}; diff --git a/tests/wpt/web-platform-tests/webxr/depth-sensing/staleViewsTests.js b/tests/wpt/web-platform-tests/webxr/depth-sensing/staleViewsTests.js new file mode 100644 index 00000000000..b1f11c9651d --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/depth-sensing/staleViewsTests.js @@ -0,0 +1,39 @@ +'use strict'; + +const staleViewsTestFunctionGenerator = function(isCpuOptimized) { + return (session, controller, t, sessionObjects) => { + let done = false; + + const staleViews = new Set(); + + return session.requestReferenceSpace('viewer').then((viewerSpace) => { + const glBinding = new XRWebGLBinding(session, sessionObjects.gl); + + const secondRafCb = function(time, frame) { + for(const view of staleViews) { + t.step(() => { + assert_throws_dom("InvalidStateError", + () => isCpuOptimized ? frame.getDepthInformation(view) + : glBinding.getDepthInformation(view), + "getDepthInformation() should throw when run with stale XRView"); + }); + } + + done = true; + }; + + const firstRafCb = function(time, frame) { + const pose = frame.getViewerPose(viewerSpace); + for(const view of pose.views) { + staleViews.add(view); + } + + session.requestAnimationFrame(secondRafCb); + }; + + session.requestAnimationFrame(firstRafCb); + + return t.step_wait(() => done); + }); + }; +};
\ No newline at end of file diff --git a/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_inputSources.https.html b/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_inputSources.https.html index dcd1d717883..ca92d390fc6 100644 --- a/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_inputSources.https.html +++ b/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_inputSources.https.html @@ -2,6 +2,7 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../resources/webxr_util.js"></script> +<script src="../resources/webxr_math_utils.js"></script> <script src="../resources/webxr_test_asserts.js"></script> <script src="../resources/webxr_test_constants.js"></script> <script src="../resources/webxr_test_constants_fake_world.js"></script> diff --git a/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html b/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html index 465e1611039..a30e71949c9 100644 --- a/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html +++ b/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_refSpaces.https.html @@ -2,6 +2,7 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../resources/webxr_util.js"></script> +<script src="../resources/webxr_math_utils.js"></script> <script src="../resources/webxr_test_asserts.js"></script> <script src="../resources/webxr_test_constants.js"></script> <script src="../resources/webxr_test_constants_fake_world.js"></script> diff --git a/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html b/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html index 04df1324066..9e0347963c6 100644 --- a/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html +++ b/tests/wpt/web-platform-tests/webxr/hit-test/ar_hittest_subscription_transientInputSources.https.html @@ -2,6 +2,7 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="../resources/webxr_util.js"></script> +<script src="../resources/webxr_math_utils.js"></script> <script src="../resources/webxr_test_asserts.js"></script> <script src="../resources/webxr_test_constants.js"></script> <script src="../resources/webxr_test_constants_fake_world.js"></script> diff --git a/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_oldSession.https.html b/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_oldSession.https.html new file mode 100644 index 00000000000..7a896aa9ff7 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_oldSession.https.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src="../resources/webxr_util.js"></script> + <script src="../resources/webxr_test_constants.js"></script> + + <script> + let testName = "getLightEstimate rejects if probe is from wrong session"; + let testFunction = (session, controller, t, sessionObjects) => new Promise((resolve) => { + let staleLightProbe = null; + let newSession = null; + + function onFrame(time, frame) { + t.step(() => { + // Attempting to get a lightEstimate with a probe created for a + // different session should throw an exception. + assert_throws_dom('InvalidStateError', () => frame.getLightEstimate(staleLightProbe)); + }); + + // Cleanup the new session we made and then resolve. + resolve(newSession.end()); + } + + // Request a default lightProbe + let probeInit = {reflectionFormat: session.preferredReflectionFormat }; + session.requestLightProbe(probeInit).then((probe) => { + staleLightProbe = probe; + return session.end(); + }).then(() => { + // Need to request a new session. + navigator.xr.test.simulateUserActivation( () => { + navigator.xr.requestSession('immersive-ar', {'requiredFeatures': ['light-estimation']}) + .then((session2) => { + + let glLayer = new XRWebGLLayer(session2, sessionObjects.gl); + glLayer.context = sessionObjects.gl; + // Session must have a baseLayer or frame requests will be ignored. + session2.updateRenderState({ + baseLayer: glLayer + }); + newSession = session2; + newSession.requestAnimationFrame(onFrame); + }); + }); + }); + }); + + xr_session_promise_test( + testName, + testFunction, + IMMERSIVE_AR_DEVICE, + 'immersive-ar', + {'requiredFeatures': ['light-estimation']}); + + </script> +</body> diff --git a/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_staleFrame.https.html b/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_staleFrame.https.html new file mode 100644 index 00000000000..499a30d5611 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_staleFrame.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src="../resources/webxr_util.js"></script> + <script src="../resources/webxr_test_constants.js"></script> + + <script> + let testName = "Cannot get XrLightEstimate from stale frame"; + let testFunction = (session, controller, t) => new Promise((resolve) => { + let lightProbe = null; + let staleFrame = null; + + function onFrame(time, frame) { + // Try to get the light estimate (even if it's null), it shouldn't throw. + let estimate = frame.getLightEstimate(lightProbe); + staleFrame = frame; + + t.step_timeout(afterFrame, 10); + } + + function afterFrame() { + t.step(() => { + // Attempting to call a method on the frame outside the callback that + // originally provided it should cause it to throw an exception. + assert_throws_dom('InvalidStateError', () => staleFrame.getLightEstimate(lightProbe)); + }); + + resolve(); + } + + // Request a default lightProbe + let probeInit = {reflectionFormat: session.preferredReflectionFormat}; + session.requestLightProbe(probeInit).then((probe) => { + lightProbe = probe; + session.requestAnimationFrame(onFrame); + }); + }); + + xr_session_promise_test( + testName, + testFunction, + IMMERSIVE_AR_DEVICE, + 'immersive-ar', {'requiredFeatures': ['light-estimation']}); + + </script> +</body> diff --git a/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_valid.https.html b/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_valid.https.html new file mode 100644 index 00000000000..68c5d841fcb --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/light-estimation/xrFrame_getLightEstimate_valid.https.html @@ -0,0 +1,104 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src="../resources/webxr_util.js"></script> + <script src="../resources/webxr_test_asserts.js"></script> + <script src="../resources/webxr_test_constants.js"></script> + + <script> + let testName = "Can get XRLightEstimates during frame"; + let fakeDeviceInitParams = IMMERSIVE_AR_DEVICE; + + let fakeEstimateCoefficients = [ + 0.01, 0.02, 0.03, + 0.04, 0.05, 0.06, + 0.07, 0.08, 0.09, + 0.10, 0.11, 0.12, + 0.13, 0.14, 0.15, + 0.16, 0.17, 0.18, + 0.19, 0.20, 0.21, + 0.22, 0.23, 0.24, + 0.25, 0.26, 0.27 + ]; + + let fakeDirectionInit = { x: 1.0, y: 0.0, z: 0.0, w: 0.0 }; + let fakeIntensityInit = { x: 0.0, y: 0.0, z: 1.0, w: 1.0 }; + + let testFunction = (session, controller, t) => new Promise((resolve) => { + let lightProbe = null; + function onFrameWithNoLightEstimation(time, frame) { + let estimate = frame.getLightEstimate(lightProbe); + t.step(() => { + assert_equals(estimate, null); + }); + + controller.setLightEstimate({ + sphericalHarmonicsCoefficients: fakeEstimateCoefficients + }); + + requestSkipAnimationFrame(session, onFrameWithCoefficients); + } + + function onFrameWithCoefficients(time, frame) { + let estimate = frame.getLightEstimate(lightProbe); + t.step(() => { + assert_not_equals(estimate, null); + assert_equals(estimate.sphericalHarmonicsCoefficients.length, 27); + assert_point_approx_equals(estimate.primaryLightDirection, { x: 0.0, y: 1.0, z: 0.0, w: 0.0 }); + assert_point_approx_equals(estimate.primaryLightIntensity, { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }); + }); + + controller.setLightEstimate({ + sphericalHarmonicsCoefficients: fakeEstimateCoefficients, + primaryLightDirection: fakeDirectionInit, + }); + + requestSkipAnimationFrame(session, onFrameWithDirection); + } + + function onFrameWithDirection(time, frame) { + let estimate = frame.getLightEstimate(lightProbe); + t.step(() => { + assert_not_equals(estimate, null); + assert_equals(estimate.sphericalHarmonicsCoefficients.length, 27); + assert_point_approx_equals(estimate.primaryLightDirection, fakeDirectionInit); + assert_point_approx_equals(estimate.primaryLightIntensity, { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }); + }); + + controller.setLightEstimate({ + sphericalHarmonicsCoefficients: fakeEstimateCoefficients, + primaryLightDirection: fakeDirectionInit, + primaryLightIntensity: fakeIntensityInit + }); + + requestSkipAnimationFrame(session, onFrameWithDirectionAndIntensity); + } + + function onFrameWithDirectionAndIntensity(time, frame) { + let estimate = frame.getLightEstimate(lightProbe); + t.step(() => { + assert_not_equals(estimate, null); + assert_equals(estimate.sphericalHarmonicsCoefficients.length, 27); + assert_point_approx_equals(estimate.primaryLightDirection, fakeDirectionInit); + assert_point_approx_equals(estimate.primaryLightIntensity, fakeIntensityInit); + }); + + resolve(); + } + + // Request a default lightProbe + session.requestLightProbe({reflectionFormat: session.preferredReflectionFormat }).then((probe) => { + lightProbe = probe; + session.requestAnimationFrame(onFrameWithNoLightEstimation); + }); + }); + + xr_session_promise_test( + testName, + testFunction, + IMMERSIVE_AR_DEVICE, + 'immersive-ar', {'requiredFeatures': ['light-estimation']}); + + </script> +</body> diff --git a/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_ended.https.html b/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_ended.https.html new file mode 100644 index 00000000000..cc046499f97 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_ended.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src="../resources/webxr_util.js"></script> + <script src="../resources/webxr_test_constants.js"></script> + + <script> + xr_session_promise_test( + "getLightProbe rejects on an ended session", + (session, controller, t) => { + return session.end().then(() => { + return promise_rejects_dom(t, "InvalidStateError", session.requestLightProbe()) + }) + }, + IMMERSIVE_AR_DEVICE, + 'immersive-ar', + {'requiredFeatures': ['light-estimation']}); + + </script> +</body> diff --git a/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_notEnabled.https.html b/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_notEnabled.https.html new file mode 100644 index 00000000000..23fe1c6ec5c --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_notEnabled.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src="../resources/webxr_util.js"></script> + <script src="../resources/webxr_test_constants.js"></script> + + <script> + let fakeDeviceInitParams = IMMERSIVE_AR_DEVICE; + + xr_session_promise_test( + "getLightProbe rejects if not enabled on session", + (session, controller, t) => promise_rejects_dom(t, "NotSupportedError", session.requestLightProbe()), + IMMERSIVE_AR_DEVICE, + 'immersive-ar'); + + </script> +</body> diff --git a/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_valid.https.html b/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_valid.https.html new file mode 100644 index 00000000000..074c7fd1dcf --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/light-estimation/xrSession_getLightProbe_valid.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src="../resources/webxr_util.js"></script> + <script src="../resources/webxr_test_constants.js"></script> + + <script> + let testName = "Can create valid XRLightProbe objects"; + + function testFunction(session, controller, t) { + // Request a default lightProbe + let defaultProbe = session.requestLightProbe(); + let srgba8Probe = session.requestLightProbe({reflectionFormat: "srgba8"}); + let preferredProbe = session.requestLightProbe({reflectionFormat: session.preferredReflectionFormat }); + + return Promise.all([defaultProbe, srgba8Probe, preferredProbe]); + } + + xr_session_promise_test( + testName, + testFunction, + IMMERSIVE_AR_DEVICE, + 'immersive-ar', {'requiredFeatures': ['light-estimation']}); + + </script> +</body> diff --git a/tests/wpt/web-platform-tests/webxr/light-estimation/xrWebGLBinding_getReflectionCubeMap.https.html b/tests/wpt/web-platform-tests/webxr/light-estimation/xrWebGLBinding_getReflectionCubeMap.https.html new file mode 100644 index 00000000000..b46f44881d5 --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/light-estimation/xrWebGLBinding_getReflectionCubeMap.https.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<body> + <script src=/resources/testharness.js></script> + <script src=/resources/testharnessreport.js></script> + <script src="../resources/webxr_util.js"></script> + <script src="../resources/webxr_test_constants.js"></script> + + <script> + let testName = "Test that getReflectionCubeMap returns or throws appropriately without a reflection map."; + + let testFunction = (session, controller, t, sessionObjects) => new Promise((resolve) => { + let debug = xr_debug.bind(this, 'testFunction'); + let lightProbe1 = null; + let binding1 = new XRWebGLBinding(session, sessionObjects.gl); + + // Request a default lightProbe + session.requestLightProbe({reflectionFormat: session.preferredReflectionFormat }).then((probe) => { + // Stash and end session. + lightProbe1 = probe; + + debug("Querying first pair"); + t.step(() => { + assert_equals( + binding1.getReflectionCubeMap(lightProbe1), + null, + "Active binding and light probe shouldn't throw when requesting cube map"); + }); + + return session.end(); + }).then(() => { + // Need to request a new session. + navigator.xr.test.simulateUserActivation( () => { + navigator.xr.requestSession('immersive-ar', { 'requiredFeatures': ['light-estimation'] }) + .then((newSession) => { + let newBinding = new XRWebGLBinding(newSession, sessionObjects.gl); + newSession.requestLightProbe({ reflectionFormat: newSession.preferredReflectionFormat }).then((newProbe) => { + t.step(() => { + debug("Querying second pair"); + assert_equals( + newBinding.getReflectionCubeMap(newProbe), + null, + "Newly created binding and light probe shouldn't throw"); + + debug("Querying old pair"); + assert_throws_dom( + "InvalidStateError", + () => binding1.getReflectionCubeMap(lightProbe1), + "Binding created with an ended session should throw InvalidStateError"); + debug("Querying mismatched pair"); + assert_throws_dom( + "InvalidStateError", + () => newBinding.getReflectionCubeMap(lightProbe1), + "Querying binding with a probe with a different backing session should throw InvalidStateError"); + }); + debug("losing context"); + + // Trigger a context loss and verify that we are unable to get the reflectionCubeMap. + let lose_context_ext = sessionObjects.gl.getExtension('WEBGL_lose_context'); + + sessionObjects.gl.canvas.addEventListener('webglcontextlost', (ev) => { + ev.preventDefault(); + + t.step(() => { + assert_throws_dom( + "InvalidStateError", + () => newBinding.getReflectionCubeMap(newProbe), + "Querying for reflection cube map on a binding with context loss should throw InvalidStateError"); + }); + + resolve(newSession.end()); + }); + + lose_context_ext.loseContext(); + }); // Request second light probe + }); // Request second session + }); // SimulateUserActivation + }); // .then on session end + }); // testFunction + + xr_session_promise_test( + testName, + testFunction, + IMMERSIVE_AR_DEVICE, + 'immersive-ar', + {'requiredFeatures': ['light-estimation']}); + + </script> +</body> diff --git a/tests/wpt/web-platform-tests/webxr/resources/webxr_math_utils.js b/tests/wpt/web-platform-tests/webxr/resources/webxr_math_utils.js index bb55fb72978..54a61c18546 100644 --- a/tests/wpt/web-platform-tests/webxr/resources/webxr_math_utils.js +++ b/tests/wpt/web-platform-tests/webxr/resources/webxr_math_utils.js @@ -17,6 +17,12 @@ let normalize_quaternion = function(input) { return {x : input.x / length, y : input.y / length, z : input.z / length, w : input.w / length}; } +// Returns negated quaternion. +// |input| - point-like dict (must have x, y, z, w) +let flip_quaternion = function(input) { + return {x : -input.x, y : -input.y, z : -input.z, w : -input.w}; +} + // |input| - point-like dict (must have x, y, z, w) let conjugate_quaternion = function(input) { return {x : -input.x, y : -input.y, z : -input.z, w : input.w}; diff --git a/tests/wpt/web-platform-tests/webxr/resources/webxr_test_asserts.js b/tests/wpt/web-platform-tests/webxr/resources/webxr_test_asserts.js index 38f1364ce03..a82f7aaf90a 100644 --- a/tests/wpt/web-platform-tests/webxr/resources/webxr_test_asserts.js +++ b/tests/wpt/web-platform-tests/webxr/resources/webxr_test_asserts.js @@ -1,8 +1,22 @@ // Utility assert functions. // Relies on resources/testharness.js to be included before this file. // Relies on webxr_test_constants.js to be included before this file. +// Relies on webxr_math_utils.js to be included before this file. -// |p1|, |p2| - objects with x, y, z, w components that are floating point numbers + +// |p1|, |p2| - objects with x, y, z, w components that are floating point numbers. +// Returns the name of mismatching component between p1 and p2. +const get_mismatched_component = function(p1, p2, epsilon = FLOAT_EPSILON) { + for (const v of ['x', 'y', 'z', 'w']) { + if (Math.abs(p1[v] - p2[v]) > epsilon) { + return v; + } + } + + return null; +} + +// |p1|, |p2| - objects with x, y, z, w components that are floating point numbers. // |epsilon| - float specifying precision // |prefix| - string used as a prefix for logging const assert_point_approx_equals = function(p1, p2, epsilon = FLOAT_EPSILON, prefix = "") { @@ -13,13 +27,7 @@ const assert_point_approx_equals = function(p1, p2, epsilon = FLOAT_EPSILON, pre assert_not_equals(p1, null, prefix + "p1 must be non-null"); assert_not_equals(p2, null, prefix + "p2 must be non-null"); - let mismatched_component = null; - for (const v of ['x', 'y', 'z', 'w']) { - if (Math.abs(p1[v] - p2[v]) > epsilon) { - mismatched_component = v; - break; - } - } + const mismatched_component = get_mismatched_component(p1, p2, epsilon); if (mismatched_component !== null) { let error_message = prefix + ' Point comparison failed.\n'; @@ -30,7 +38,31 @@ const assert_point_approx_equals = function(p1, p2, epsilon = FLOAT_EPSILON, pre } }; -// |p1|, |p2| - objects with x, y, z, w components that are floating point numbers +const assert_orientation_approx_equals = function(q1, q2, epsilon = FLOAT_EPSILON, prefix = "") { + if (q1 == null && q2 == null) { + return; + } + + assert_not_equals(q1, null, prefix + "q1 must be non-null"); + assert_not_equals(q2, null, prefix + "q2 must be non-null"); + + const q2_flipped = flip_quaternion(q2); + + const mismatched_component = get_mismatched_component(q1, q2, epsilon); + const mismatched_component_flipped = get_mismatched_component(q1, q2_flipped, epsilon); + + if (mismatched_component !== null && mismatched_component_flipped !== null) { + // q1 doesn't match neither q2 nor -q2, so it definitely does not represent the same orientations, + // log an assert failure. + let error_message = prefix + ' Orientation comparison failed.\n'; + error_message += ` p1: {x: ${q1.x}, y: ${q1.y}, z: ${q1.z}, w: ${q1.w}}\n`; + error_message += ` p2: {x: ${q2.x}, y: ${q2.y}, z: ${q2.z}, w: ${q2.w}}\n`; + error_message += ` Difference in component ${mismatched_component} exceeded the given epsilon.\n`; + assert_approx_equals(q2[mismatched_component], q1[mismatched_component], epsilon, error_message); + } +} + +// |p1|, |p2| - objects with x, y, z, w components that are floating point numbers. // |epsilon| - float specifying precision // |prefix| - string used as a prefix for logging const assert_point_significantly_not_equals = function(p1, p2, epsilon = FLOAT_EPSILON, prefix = "") { @@ -38,13 +70,7 @@ const assert_point_significantly_not_equals = function(p1, p2, epsilon = FLOAT_E assert_not_equals(p1, null, prefix + "p1 must be non-null"); assert_not_equals(p2, null, prefix + "p2 must be non-null"); - let mismatched_component = null; - for (const v of ['x', 'y', 'z', 'w']) { - if (Math.abs(p1[v] - p2[v]) > epsilon) { - mismatched_component = v; - break; - } - } + let mismatched_component = get_mismatched_component(p1, p2, epsilon); if (mismatched_component === null) { let error_message = prefix + ' Point comparison failed.\n'; @@ -67,7 +93,7 @@ const assert_transform_approx_equals = function(t1, t2, epsilon = FLOAT_EPSILON, assert_not_equals(t2, null, prefix + "t2 must be non-null"); assert_point_approx_equals(t1.position, t2.position, epsilon, prefix + "positions must be equal"); - assert_point_approx_equals(t1.orientation, t2.orientation, epsilon, prefix + "orientations must be equal"); + assert_orientation_approx_equals(t1.orientation, t2.orientation, epsilon, prefix + "orientations must be equal"); }; // |m1|, |m2| - arrays of floating point numbers diff --git a/tests/wpt/web-platform-tests/webxr/resources/webxr_test_constants.js b/tests/wpt/web-platform-tests/webxr/resources/webxr_test_constants.js index 40643d0c6ab..7dbedd92419 100644 --- a/tests/wpt/web-platform-tests/webxr/resources/webxr_test_constants.js +++ b/tests/wpt/web-platform-tests/webxr/resources/webxr_test_constants.js @@ -125,6 +125,7 @@ const ALL_FEATURES = [ 'dom-overlay', 'light-estimation', 'anchors', + 'depth-sensing', ]; const TRACKED_IMMERSIVE_DEVICE = { diff --git a/tests/wpt/web-platform-tests/webxr/resources/webxr_test_constants_fake_depth.js b/tests/wpt/web-platform-tests/webxr/resources/webxr_test_constants_fake_depth.js new file mode 100644 index 00000000000..36890d398de --- /dev/null +++ b/tests/wpt/web-platform-tests/webxr/resources/webxr_test_constants_fake_depth.js @@ -0,0 +1,78 @@ +'use strict'; + +// This file introduces constants used to mock depth data for depth sensing API. + +const convertDepthBufferToArrayBuffer = function (data, desiredFormat) { + if(desiredFormat == "luminance-alpha") { + const result = new ArrayBuffer(data.length * 2); // each entry has 2 bytes + const view = new Uint16Array(result); + + for(let i = 0; i < data.length; ++i) { + view[i] = data[i]; + } + + return new Uint8Array(result); + } else if(desiredFormat == "float32") { + const result = new ArrayBuffer(data.length * 4); // each entry has 4 bytes + const view = new Float32Array(result); + + for(let i = 0; i < data.length; ++i) { + view[i] = data[i]; + } + + return new Uint8Array(result); + } else { + throw new Error("Unrecognized data format!"); + } +} + +// Let's assume that the depth values are in cm, Xcm = x * 1/100m +const RAW_VALUE_TO_METERS = 1/100; + +const createDepthSensingData = function() { + const depthSensingBufferHeight = 5; + const depthSensingBufferWidth = 7; + const depthSensingBuffer = [ + 1, 1, 1, 1, 1, 1, 1, // first row + 1, 2, 3, 4, 5, 6, 7, + 1, 4, 9, 16, 25, 36, 49, + 1, 8, 27, 64, 125, 216, 343, + 1, 16, 81, 256, 625, 1296, 2401, + ]; // depthSensingBuffer value at column c, row r is Math.pow(c+1, r). + + // Let's assume that the origin of the depth buffer is in the bottom right + // corner, with X's growing to the left and Y's growing upwards. + // This corresponds to the origin at 2401 in the above matrix, with X axis + // growing from 2401 towards 1296, and Y axis growing from 2401 towards 343. + // This corresponds to a rotation around Z axis by 180 degrees, with origin at [1,1]. + const depthSensingBufferFromViewerTransform = { + position: [1, 1, 0], + orientation: [0, 0, 1, 0], + }; + + return { + depthData: convertDepthBufferToArrayBuffer(depthSensingBuffer, "luminance-alpha"), + width: depthSensingBufferWidth, + height: depthSensingBufferHeight, + normDepthBufferFromNormView: depthSensingBufferFromViewerTransform, + rawValueToMeters: RAW_VALUE_TO_METERS, + }; +}; + +const DEPTH_SENSING_DATA = createDepthSensingData(); + +// Returns expected depth value at |column|, |row| coordinates, expressed +// in depth buffer's coordinate system. +const getExpectedValueAt = function(column, row) { + return Math.pow(column+1, row) * RAW_VALUE_TO_METERS; +}; + +const VALID_DEPTH_CONFIG_CPU_USAGE = { + usagePreference: ['cpu-optimized'], + dataFormatPreference: ['luminance-alpha', 'float32'], +}; + +const VALID_DEPTH_CONFIG_GPU_USAGE = { + usagePreference: ['gpu-optimized'], + dataFormatPreference: ['luminance-alpha', 'float32'], +}; diff --git a/tests/wpt/web-platform-tests/webxr/resources/webxr_util.js b/tests/wpt/web-platform-tests/webxr/resources/webxr_util.js index 8ca7918298b..cf9c6ff880a 100644 --- a/tests/wpt/web-platform-tests/webxr/resources/webxr_util.js +++ b/tests/wpt/web-platform-tests/webxr/resources/webxr_util.js @@ -18,7 +18,7 @@ function xr_promise_test(name, func, properties, glContextType, glContextPropert // Perform any required test setup: xr_debug(name, 'setup'); - assert_implements(navigator.xr, 'missing navigator.xr'); + assert_implements(navigator.xr, 'missing navigator.xr - ensure test is run in a secure context.'); // Only set up once. if (!navigator.xr.test) { @@ -88,7 +88,8 @@ function requestSkipAnimationFrame(session, callback) { // Calls the passed in test function with the session, the controller for the // device, and the test object. function xr_session_promise_test( - name, func, fakeDeviceInit, sessionMode, sessionInit, properties, glcontextPropertiesParam, gllayerPropertiesParam) { + name, func, fakeDeviceInit, sessionMode, sessionInit, properties, + glcontextPropertiesParam, gllayerPropertiesParam) { const glcontextProperties = (glcontextPropertiesParam) ? glcontextPropertiesParam : {}; const gllayerProperties = (gllayerPropertiesParam) ? gllayerPropertiesParam : {}; @@ -133,7 +134,11 @@ function xr_session_promise_test( }); sessionObjects.glLayer = glLayer; xr_debug(name, 'session.visibilityState=' + session.visibilityState); - resolve(func(session, testDeviceController, t, sessionObjects)); + try { + resolve(func(session, testDeviceController, t, sessionObjects)); + } catch(err) { + reject("Test function failed with: " + err); + } }) .catch((err) => { xr_debug(name, 'error: ' + err); diff --git a/tests/wpt/web-platform-tests/webxr/xrRigidTransform_inverse.https.html b/tests/wpt/web-platform-tests/webxr/xrRigidTransform_inverse.https.html index cd5b34583b3..d4fdc15396d 100644 --- a/tests/wpt/web-platform-tests/webxr/xrRigidTransform_inverse.https.html +++ b/tests/wpt/web-platform-tests/webxr/xrRigidTransform_inverse.https.html @@ -2,6 +2,7 @@ <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <script src="resources/webxr_util.js"></script> +<script src="resources/webxr_math_utils.js"></script> <script src="resources/webxr_test_constants.js"></script> <script src="resources/webxr_test_asserts.js"></script> <script> diff --git a/tests/wpt/web-platform-tests/workers/same-origin-check.sub.html b/tests/wpt/web-platform-tests/workers/same-origin-check.sub.html new file mode 100644 index 00000000000..7af021b8dbb --- /dev/null +++ b/tests/wpt/web-platform-tests/workers/same-origin-check.sub.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const sameOrigin = 'http://{{host}}:{{ports[http][0]}}'; +const crossOrigin = 'http://{{host}}:{{ports[http][1]}}'; +const workerPath = '/workers/support/post-message-on-load-worker.js?pipe=header(Access-Control-Allow-Origin,*)'; +const redirectPath = '/service-workers/service-worker/resources/redirect.py?ACAOrigin=*&Redirect='; +const tests = [ + { + name: "cross-origin", + url: crossOrigin + workerPath + }, + { + name: "cross-origin-to-same-origin-redirect", + url: crossOrigin + redirectPath + + encodeURIComponent(sameOrigin + workerPath) + }, + { + name: "same-origin-to-cross-origin-redirect", + url: sameOrigin + redirectPath + encodeURIComponent(crossOrigin + workerPath) + }, + { + name: "same-origin-to-cross-origin-to-same-origin-redirect", + url: sameOrigin + redirectPath + + encodeURIComponent( + crossOrigin + redirectPath + + encodeURIComponent(sameOrigin + workerPath)) + }, +]; + +for (const test of tests) { + for (const type of ['classic', 'module']) { + promise_test(t => new Promise((resolve, reject) => { + try { + const worker = new Worker(test.url, {type}); + worker.onmessage = _ => reject('Worker loaded unexpectedly'); + worker.onerror = resolve; + } catch (e) { + resolve(); + } + }), 'Worker: ' + test.name + ' (' + type + ')'); + + promise_test(t => new Promise((resolve, reject) => { + try { + const worker = new SharedWorker( + test.url + '&label=' + type + test.name, {type}); + worker.port.onmessage = _ => reject('Worker loaded unexpectedly'); + worker.onerror = resolve; + } catch (e) { + resolve(); + } + }), 'SharedWorker: ' + test.name + ' (' + type + ')'); + } +} +</script> diff --git a/tests/wpt/web-platform-tests/xhr/access-control-basic-cors-safelisted-response-headers.htm b/tests/wpt/web-platform-tests/xhr/access-control-basic-cors-safelisted-response-headers.htm index be9a10ef016..fb58957f4b8 100644 --- a/tests/wpt/web-platform-tests/xhr/access-control-basic-cors-safelisted-response-headers.htm +++ b/tests/wpt/web-platform-tests/xhr/access-control-basic-cors-safelisted-response-headers.htm @@ -18,6 +18,7 @@ assert_not_equals(xhr.getResponseHeader("cache-control"), null); assert_not_equals(xhr.getResponseHeader("content-language"), null); assert_not_equals(xhr.getResponseHeader("content-type"), null); + assert_not_equals(xhr.getResponseHeader("content-length"), null); assert_not_equals(xhr.getResponseHeader("expires"), null); assert_not_equals(xhr.getResponseHeader("last-modified"), null); assert_not_equals(xhr.getResponseHeader("pragma"), null); diff --git a/tests/wpt/web-platform-tests/xhr/resources/last-modified.py b/tests/wpt/web-platform-tests/xhr/resources/last-modified.py index 9d694817bf1..db08e4b16d7 100644 --- a/tests/wpt/web-platform-tests/xhr/resources/last-modified.py +++ b/tests/wpt/web-platform-tests/xhr/resources/last-modified.py @@ -3,7 +3,7 @@ from wptserve.utils import isomorphic_decode, isomorphic_encode def main(request, response): import datetime, os srcpath = os.path.join(os.path.dirname(isomorphic_decode(__file__)), u"well-formed.xml") - srcmoddt = datetime.datetime.fromtimestamp(os.path.getmtime(srcpath)) + srcmoddt = datetime.datetime.utcfromtimestamp(os.path.getmtime(srcpath)) response.headers.set(b"Last-Modified", isomorphic_encode(srcmoddt.strftime(u"%a, %d %b %Y %H:%M:%S GMT"))) response.headers.set(b"Content-Type", b"application/xml") return open(srcpath, u"r").read() diff --git a/tests/wpt/web-platform-tests/xhr/responsexml-document-properties.htm b/tests/wpt/web-platform-tests/xhr/responsexml-document-properties.htm index cb2edb777dd..a9f14f8212a 100644 --- a/tests/wpt/web-platform-tests/xhr/responsexml-document-properties.htm +++ b/tests/wpt/web-platform-tests/xhr/responsexml-document-properties.htm @@ -46,6 +46,14 @@ }, name) } + // Parse a "lastModified" value and convert it to a Date. + // See https://html.spec.whatwg.org/multipage/dom.html#dom-document-lastmodified + function parseLastModified(value) { + const [undefined, month, day, year, hours, minutes, seconds] = + /^(\d\d)\/(\d\d)\/(\d+) (\d\d):(\d\d):(\d\d)$/.exec(value); + return new Date(year, month - 1, day, hours, minutes, seconds); + } + async_test(t => { const client = new XMLHttpRequest(); client.open("GET", "resources/redirect.py?location=well-formed.xml"); @@ -79,7 +87,7 @@ }, "Test document URL properties of document with <base> after redirect"); test(function() { - var lastModified = Math.floor(new Date(client.responseXML.lastModified).getTime() / 1000); + var lastModified = Math.floor(parseLastModified(client.responseXML.lastModified).getTime() / 1000); var now = Math.floor(new Date().getTime(new Date().getTime() + 3000) / 1000); // three seconds from now, in case there's clock drift assert_greater_than_equal(lastModified, timePreXHR); assert_less_than_equal(lastModified, now); @@ -89,7 +97,7 @@ var client2 = new XMLHttpRequest() client2.open("GET", "resources/last-modified.py", false) client2.send(null) - assert_equals((new Date(client2.getResponseHeader('Last-Modified'))).getTime(), (new Date(client2.responseXML.lastModified)).getTime()) + assert_equals((new Date(client2.getResponseHeader('Last-Modified'))).getTime(), (parseLastModified(client2.responseXML.lastModified)).getTime()) }, 'lastModified set to related HTTP header if provided') test(function() { |